A PhotoPrism® Portal stores a single branding theme and distributes it to every instance in the cluster. A theme bundles the colors, login-page wallpaper, logo, favicon, and sign-in button icons, so a branding change made once on the Portal propagates to all instances automatically.
Theme assets are served under the /_theme/ URL path on each node. A theme is identified by an app.js entry file and an optional version.txt; the Portal never distributes a theme that has no app.js.
Theme Directories
Themes live under the configuration directory (storage/config/ by default). Three locations matter, depending on the node role:
| Directory | Role | Purpose |
|---|---|---|
storage/config/theme/ |
any | The active theme directory served at /_theme/. Overridable with PHOTOPRISM_THEME_PATH. |
storage/config/portal/theme/ |
Portal | The Portal’s canonical cluster theme — the one it distributes. Used when it contains app.js. |
storage/config/node/theme/ |
instance | Where an instance stores the Portal-provided theme after downloading it. |
Resolution rules:
- On the Portal, the distributed theme is taken from
portal/theme/when that directory exists and contains a non-emptyapp.js; otherwise the Portal falls back to its owntheme/directory. - On an instance, when
node/theme/holds a valid theme (a non-emptyapp.js, so a version can be detected), the instance switches its active theme path tonode/theme/. The cluster theme therefore takes effect without touching the instance’s owntheme/directory.
Two marker files drive the workflow: app.js must be present and non-empty for a theme to be considered valid and distributable, and version.txt carries the version string the Portal and instances compare to decide when a refresh is needed.
What a Theme Contains
Theme files are served under /_theme/ and referenced by a few configuration options. PhotoPrism resolves each asset from the active theme directory when the named file is present:
| Asset | Option / detection | Notes |
|---|---|---|
| Theme entry | app.js |
Required marker; carries custom CSS and color tokens. An optional version.txt sits beside it. |
| Login wallpaper | auto-detected | The first *.webp, then *.avif, then *.jpg file in the theme directory becomes the login background. |
| App icon / logo | PHOTOPRISM_APP_ICON |
When the named file exists in the theme directory, it is served as the app logo / PWA icon. |
| Favicon | PHOTOPRISM_SITE_FAVICON |
Resolved from the theme directory when present, otherwise the built-in favicon. |
| Share preview | PHOTOPRISM_SITE_PREVIEW |
Social/sharing preview image, served from the theme directory. |
| Sign-in button | PHOTOPRISM_OIDC_ICON |
Icon shown on the “Continue with <provider>” button, served from the theme directory. |
Downloadable theme archives are validated for safety: only an allowlist of file types is accepted, archive size and entry counts are capped, path traversal is rejected, and private-network download sources are disallowed by default.
Login Page & Logo Customization
To rebrand the login page and logos across the cluster, place the assets in the Portal’s theme directory and let provisioning distribute them:
- Login background: drop a
*.webp(preferred),*.avif, or*.jpgimage into the theme directory — the first match becomes the login wallpaper on every instance. - Logo / app icon: add the logo file and point
PHOTOPRISM_APP_ICONat it. - Favicon and share preview: set
PHOTOPRISM_SITE_FAVICONandPHOTOPRISM_SITE_PREVIEWto files in the theme directory. - Colors and CSS: ship them in
app.js, so contrast and brand colors can be tuned without rebuilding the application image.
For replacing larger static assets (for example a fully custom login or registration page), a node can also serve files from a storage/web overlay directory — see Web Overlay. The theme mechanism is the cluster-distributed path; the web overlay is node-local static content.
How Provisioning Works
The Portal is the source of truth for the cluster theme. Distribution happens in four steps:
- Seed the Portal theme. On startup, if
PHOTOPRISM_THEME_URLis set and the Portal’s theme directory is empty, the Portal downloads the archive and installs it. If the directory already contains files, the auto-install is skipped — a manually installed or customized theme is never overwritten. - Portal serves the theme. The Portal exposes the theme as a zip at
GET /api/v1/cluster/theme, built from its canonical theme directory (requires a non-emptyapp.js). Requests are allowed from the cluster network range or with an authenticated cluster download permission. - Instances download and refresh. When an instance registers or boots, it compares the Portal-advertised theme version with the version installed in its
node/theme/directory:- No local
app.js→ download. - Local version differs from the Portal version → download and overwrite
node/theme/. - Versions match → keep the installed theme. The download needs the instance’s cluster (OAuth) credentials; it is skipped while those are unavailable.
- No local
- Instances activate the theme. Once
node/theme/holds a valid theme, the instance switches its active theme path to it, so the cluster branding is applied.
Because refreshes are version-based, bump the theme’s version.txt when you change branding — instances only re-download when the Portal version differs from their installed one.
Seeding the Theme from a URL
Set PHOTOPRISM_THEME_URL on the Portal to an archive (.zip) that contains the theme files at its root (including app.js). The Portal installs it on first start when no theme is present:
services:
portal:
image: photoprism/portal:latest
environment:
PHOTOPRISM_NODE_ROLE: "portal"
PHOTOPRISM_THEME_URL: "https://cdn.example.com/themes/acme-theme.zip"
Leave PHOTOPRISM_THEME_URL empty to disable the auto-install and manage the theme directory yourself. See Config Options for the related settings.
Pulling the Theme Manually
You can download the current Portal theme to an instance from the command line. The pull command installs into config/theme by default, or a directory you pass with --dest. If only a join token is provided, it registers the node first to obtain credentials, then downloads:
photoprism cluster theme pull --dest /photoprism/storage/config/node/theme
PhotoPrism® Documentation
For more information on specific features, services and related resources, please refer to the other documentation available in our Knowledge Base and User Guide: