# Configuration
> [!TIP]
> You can see all of the configurable options by [looking at the default config.json](https://github.com/alangrainger/immich-public-proxy/blob/main/app/config.json). A description of each of these options is below.
## Contents
- [How to provide a config override](#how-to-provide-a-config-override)
- [How options are organised](#how-options-are-organised)
- [IPP options](#ipp-options)
- [Gallery](#gallery)
- [Lightbox](#lightbox)
- [Metadata](#metadata)
- [Description](#description)
- [EXIF group](#exif-group)
- [Location group](#location-group)
- [Customising your error response pages](#customising-your-error-response-pages)
- [Migration notes for upgraders](#migration-notes-for-upgraders)
## How to provide a config override
> [!NOTE]
> You only need to include the keys you're changing. Anything you omit keeps its default. The defaults in IPP are always the most privacy-respecting option.
There are two ways to supply custom config:
### Mount a file
Recommended for anything non-trivial. Make a copy of [config.json](https://github.com/alangrainger/immich-public-proxy/blob/main/app/config.json) next to your `docker-compose.yml`, edit, then add a volume:
```yaml
volumes:
- ./config.json:/app/config.json
```
Restart the container and your custom configuration becomes active.
### Or inline via env var
For one-off or single-key overrides. See [inline configuration](inline-configuration.md).
## How options are organised
All options live under `ipp.*`. The metadata groups (`ipp.showMetadata.exif`, `ipp.showMetadata.location`) are per-field opt-in: every field is off until you explicitly set its flag to `true`. Nothing is automatically exposed - including fields IPP adds in future versions.
You only need to include the keys you're changing. Anything you omit keeps its default.
## IPP options
Top-level options under `ipp.*`.
| Option | Type | Description |
|---------------------------------------|----------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| `responseHeaders` | `object` | Change the headers sent with your web responses. By default there is `cache-control` and CORS added. |
| `maxDownloadQuality` | `string` | Highest quality served for a download (the download button and "download all" zip).
`"original"` the full-resolution original file (default)
`"fullsize"` full resolution but always browser-displayable - the original for JPEG/PNG/WebP, Immich's converted JPEG for RAW/HEIF
`"preview"` only the ~1440px preview JPEG. |
| `maxZoomQuality` | `string` | Highest quality the lightbox loads when you zoom in past fit-to-screen.
`"preview"` keep the preview (default; zoom is capped to the preview's real pixels)
`"fullsize"` upgrade to the full-resolution browser-displayable image on zoom, à la the Immich web viewer. `"original"` is intentionally not an option (it could be an unviewable RAW/DNG or a huge file).
**Independent of `allowDownload`** (your download UI can be off while zoom is on). It only does anything where Immich can serve full resolution: the share's **own** "allow downloads" toggle in Immich must be on (the full-res image comes from the original file, which Immich gates on that toggle), and the format must be **web-displayable** (JPEG/PNG/WebP). Otherwise (RAW/HEIF, or a share with Immich downloads off) the lightbox stays on the preview. To enable zoom-up, leave the Immich share's download permission on and use `allowDownload` to control whether the download buttons appear. |
| `downloadedFilename` | `int` | The filename of the downloaded image.
`0` for the original filename if available, falling back to the Immich asset ID
`1` for the Immich asset ID number
`2` for a shortened version of the asset ID: `img_` plus the first 8 characters of the asset ID |
| `allowDownload` | `int` | Show the download UI - the "download all" zip, the multi-select download, and the per-asset download button in the lightbox. Purely a UI switch; it has no effect on image quality (see `maxZoomQuality` / `maxDownloadQuality`).
`0` downloads off
`1` follow the Immich share's own download setting ([example](https://github.com/user-attachments/assets/79ea8c08-71ce-42ab-b025-10aec384938a))
`2` always on. |
| `downloadFromImmichConcurrencyLimit` | `int` | Maximum number of assets IPP will fetch from your Immich server in parallel when building a "download all" zip. Defaults to `8`. Lower this if your Immich server is slow or you see download timeouts on large albums; raise it for faster downloads if your server can handle the load. |
| `allowSlugLinks` | `bool` | Enable/disable the custom URL links. |
| `showHomePage` | `bool` | Set to `false` to remove the IPP shield page at `/` and at `/share`. |
| `gallery` | `object` | Gallery-page options. See [Gallery](#gallery). |
| `lightbox` | `object` | Lightbox options. See [Lightbox](#lightbox). |
| `showMetadata` | `object` | Description / EXIF / location reveal controls. See [Metadata](#metadata). |
| `customInvalidResponse` | various | Send a custom response instead of the default 404. See [Custom responses](custom-responses.md). |
For example, to disable the home page at `/` and at `/share`:
```json
{
"ipp": {
"showHomePage": false
}
}
```
## Gallery
Options that control how the gallery page is rendered. Configured under `ipp.gallery`.
| Option | Type | Description |
|----------------------|--------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| `singleImage` | `bool` | By default a link to a single image will directly open the image file. Set to `true` if you want to show a gallery page instead for a single item. |
| `singleItemAutoOpen` | `bool` | When a share contains a single item and is opened on its gallery page, automatically open the lightbox on the asset. Default `true`. |
| `showTitle` | `bool` | Show a title on the gallery page. This is taken from the album title if it is an album being shared, otherwise the "Description" from the shared link will be used. |
| `showDescription` | `bool` | Show the album description below the title. This only applies if it is an album which is being shared. |
| `showExpiryDate` | `bool` | Show the share's expiry date next to the item count in the subtitle, as `45 items · available until 2026-07-10`. Only appears when the share actually has an expiry set in Immich (shares set to "never" show nothing extra). Default `false`. |
| `expiryDateFormat` | `string` | Format for the `showExpiryDate` date, as a [dayjs format string](https://day.js.org/docs/en/display/format). Default `"YYYY-MM-DD"` (ISO 8601 date, e.g. `2026-07-10`). Example: `"D MMMM YYYY"` renders `10 July 2026`. Name-based tokens (`MMMM`, `dddd`, ...) render in English unless you also set `expiryDateLocale`. |
| `expiryDateLocale` | `string` | Locale for `showExpiryDate` when the format uses name tokens (e.g. `MMMM` for month names), as a [dayjs locale code](https://github.com/iamkun/dayjs/tree/dev/src/locale) such as `"de"`, `"fr"` or `"en-gb"`. Default `""` (dayjs's default English). The numeric default format needs no locale. Applies only to this server-rendered date; thumbnail date-group headers (`groupByDate`) already follow each viewer's own browser locale. |
| `groupByDate` | `bool` or `string` | Group the gallery's thumbnails by date, with a header above each group. Set to `false` (default, no grouping), `true` / `"month"` for month headers like "December 2024", or `"day"` for day headers like "25 December 2024". Grouping uses each photo's local "taken" date (matching Immich's own timeline), and sorts photos newest-first. Items missing a creation date end up under an "Undated" bucket at the end (or render without any header when the whole gallery is undated). **Requires "Show metadata" to be enabled on the share in Immich** - when that's off, Immich strips creation dates from the assets it returns to IPP, so grouping won't work. |
| `cacheTime` | `int` | How long (in seconds) browsers and any CDN may cache the gallery **page** before revalidating. Set this longer to reduce load on your server, but be aware that if you add new photos to a gallery they won't show up until the cache times out or you manually clear any downstream caches. |
Example: show the gallery title and group photos by month.
```json
{
"ipp": {
"gallery": {
"showTitle": true,
"groupByDate": true
}
}
}
```
## Lightbox
The gallery's lightbox is powered by [PhotoSwipe](https://photoswipe.com/). Configured under `ipp.lightbox`.
| Option | Type | Description |
|------------------|----------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| `showArrows` | `bool` | Show the prev/next arrows on desktop. They appear when the user hovers the lightbox. Default `true`. |
| `showDownload` | `bool` | Show a download button in the lightbox toolbar. Only takes effect when downloads are also allowed by `allowDownload`. Default `true`. |
| `mobileArrows` | `bool` | Show prev/next arrows on mobile (under 640px viewport). Off by default since swipe is the natural mobile navigation. |
| `autoPlayVideos` | `bool` | Automatically play videos when their slide opens or becomes active. If the browser blocks unmuted autoplay (e.g. when deep-linking straight to a video), playback falls back to muted. Default `false`. |
| `options` | `object` | Custom [PhotoSwipe options](https://photoswipe.com/options/) to override defaults (e.g. `{"wheelToZoom": true}`). |
Example: hide the download button inside the lightbox even though zip downloads are otherwise allowed.
```json
{
"ipp": {
"lightbox": {
"showDownload": false
}
}
}
```
## Metadata
Configured under `ipp.showMetadata`. The lightbox includes a slide-in info sidebar (toggle with the **i** key or the info button in the toolbar) that surfaces whatever metadata you opt into here.
| Option | Type | Description |
|---------------|----------|------------------------------------------------------------------------------------------------------------------------------------|
| `description` | `object` | Where to show the description. `{ "caption": bool, "sidebar": bool }`. Both default `false`. See [Description](#description). |
| `exif` | `object` | Camera / file EXIF group. Per-field opt-in flags, all default `false`. See [EXIF group](#exif-group). |
| `location` | `object` | Location group (city / state / country / GPS). Per-field opt-in flags, all default `false`. See [Location group](#location-group). |
Every per-field flag defaults to `false`. A field is sent to the client only when its flag is explicitly `true`. There is no master switch - this is deliberate, so that fields IPP adds in future releases don't auto-expose without your action.
The share owner's **"Show metadata"** toggle in Immich takes precedence over everything here: when it's off,
IPP suppresses all description / EXIF / location output and hides the info sidebar (and its toolbar toggle)
entirely, regardless of these settings. Note that with "Show metadata" off Immich also strips file creation
dates from non-album shares, so [date grouping](#gallery) won't work for those shares.
The info sidebar (and its toolbar toggle button) only appear when there is at least one section the operator has opted into - i.e. `description.sidebar` is true, or `exif` / `location` has at least one flag set to `true`. With all three off, the sidebar UI is suppressed.
### Description
Under `ipp.showMetadata.description`.
| Option | Type | Description |
|-----------|--------|--------------------------------------------------------------------------|
| `caption` | `bool` | Show the description below the photo as a lightbox caption. |
| `sidebar` | `bool` | Show the description at the top of the info sidebar. |
Set both to show in both places; set neither and the description is not included at all.
### EXIF group
Under `ipp.showMetadata.exif`.
| Option | Type | Description |
|---------------------|--------|--------------------------------------------------------------------------|
| `dateTimeOriginal` | `bool` | Show the date the photo was taken (per EXIF). |
| `fileName` | `bool` | Show the original filename. |
| `dimensions` | `bool` | Show width x height and megapixel count. |
| `fileSize` | `bool` | Show the file size. |
| `make` | `bool` | Camera manufacturer (e.g. "Canon"). |
| `model` | `bool` | Camera model (e.g. "EOS R5"). |
| `lensModel` | `bool` | Lens model. |
| `exposureTime` | `bool` | Shutter speed (e.g. "1/200"). |
| `iso` | `bool` | ISO sensitivity. |
| `fNumber` | `bool` | Aperture f-number. |
| `focalLength` | `bool` | Focal length in millimetres. |
### Location group
Under `ipp.showMetadata.location`.
| Option | Type | Description |
|------------|--------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| `city` | `bool` | Show city. |
| `state` | `bool` | Show state / region. |
| `country` | `bool` | Show country. |
| `gps` | `bool` | Show GPS coordinates. |
| `webLink` | `bool` | Show an "Open in OpenStreetMap" link below the coordinates. The link is rendered with `rel="noreferrer"` so the share URL is not leaked to the map provider when a viewer clicks it. Has no effect unless `gps` is also true. Default `true`. |
A full example showing the description in the sidebar only (not as a lightbox caption), exposing camera EXIF, and revealing place names but not the GPS coordinates:
```json
{
"ipp": {
"showMetadata": {
"description": { "caption": false, "sidebar": true },
"exif": {
"dateTimeOriginal": true,
"make": true,
"model": true,
"lensModel": true,
"exposureTime": true,
"iso": true,
"fNumber": true,
"focalLength": true
},
"location": { "city": true, "state": true, "country": true }
}
}
}
```
## Customising your error response pages
You can customise the responses that IPP sends for invalid requests. For example you could:
- Drop the connection entirely (no response).
- Redirect to a new website.
- Send a different status code.
- Send a custom 404 page.
See [Custom responses](custom-responses.md) for the full reference.
## Migration notes for upgraders
A few config keys have been renamed across versions. Existing configs continue to work via backward-compatibility shims and a startup deprecation notice. The shims will be removed in v3.0.
**Gallery keys moved under `ipp.gallery.*` (v2.0).** The old top-level keys are mapped automatically:
| Legacy key | New key |
|------------------------------|----------------------------------|
| `ipp.singleImageGallery` | `ipp.gallery.singleImage` |
| `ipp.singleItemAutoOpen` | `ipp.gallery.singleItemAutoOpen` |
| `ipp.showGalleryTitle` | `ipp.gallery.showTitle` |
| `ipp.showGalleryDescription` | `ipp.gallery.showDescription` |
| `ipp.groupGalleryByDate` | `ipp.gallery.groupByDate` |
**`showMetadata.description` is now an object.** It used to be a single boolean. A legacy `description: true` migrates to `{ caption: true, sidebar: true }` (and `false` to both `false`). Move to the explicit form to silence the deprecation notice and so you can target the caption vs. the sidebar independently.
**`showMetadata.{exif,location}.enabled` was removed (v2.3.1).** Per-field flags are now the only gate, and all default to `false`. The shim preserves behaviour for legacy configs by rewriting them in memory at startup:
- Legacy `enabled: true` defaults every unset per-field flag in the group to `true`, matching the old "everything visible" behaviour. Per-field flags you had explicitly set to `false` (the "all except X" pattern) are kept.
- Legacy `enabled: false` clears any per-field flags that were `true`, matching the old "nothing visible" behaviour and stopping leftover documentation flags from the shipped 2.3 config from becoming live opt-ins.
A deprecation notice is printed at startup. Update your `config.json` to the explicit per-field form to silence it. The shim will be removed in v3.0, and at that point any field IPP has added since v2.3 will need a fresh opt-in.
**`downloadOriginalPhoto` -> `maxDownloadQuality` (v3.0).** The boolean becomes the quality tier: `true` maps to `"original"`, `false` to `"preview"`. The shim will be removed in v4.0.
**`allowDownloadAll` -> `allowDownload` (v3.0).** Renamed, same `0` / `1` / `2` values. The shim will be removed in v4.0.