Skip to main content
Version: v2

Theme overrides via environment variables

For most branding use cases, the app.branding.theme.* configuration in app-config.local.yaml (see Simple Branding) is all you need. For low-level JSON overrides of the internal theme file (/app/packages/app/dist/theme.json), the image entrypoint supports dedicated environment variables.

Environment variable reference

VariableEffect
THEME_DOWNLOAD_URLURL to a JSON file. The entrypoint downloads it and replaces the internal theme file completely.
THEME_CUSTOM_JSONInline JSON string. Merged with or replaces the internal theme file, depending on THEME_MERGE_JSON.
THEME_MERGE_JSONSet to "false" to replace the theme file entirely with THEME_CUSTOM_JSON. Defaults to merge (any value other than "false").
THEME_FAV_ICONURL to a favicon. The entrypoint downloads it and replaces /app/packages/app/dist/favicon.ico.
Validate before deploying

Malformed JSON in THEME_CUSTOM_JSON makes the entrypoint silently skip the override — the container still boots, but the theme doesn't change and nothing in the logs points at the JSON. Validate before deploying:

echo "$THEME_CUSTOM_JSON" | jq .

The favicon download happens before the backend starts, and the browser renders it based on the remote server's Content-Type header rather than the URL's file extension — a THEME_FAV_ICON URL ending in .ico, .png, or .svg all work as long as the server serves the right content type.

Example: partial theme override via Docker Compose

services:
devportal:
image: veecode/devportal:2.1.3
ports:
- "7007:7007"
environment:
- VEECODE_PRESETS=recommended,veecode-theme,github,github-auth
- GITHUB_PAT
- GITHUB_ORG
- GITHUB_AUTH_CLIENT_ID
- GITHUB_AUTH_CLIENT_SECRET
- THEME_MERGE_JSON=true
- |
THEME_CUSTOM_JSON={
"light": {
"background": {
"default": "#efefef"
}
},
"dark": {
"background": {
"default": "#202020"
}
}
}
volumes:
- dp-data:/app/data
- dp-plugins:/app/dynamic-plugins-root

volumes:
dp-data:
dp-plugins:

Example: custom theme from a URL

environment:
- THEME_DOWNLOAD_URL=https://example.com/my-theme.json

The downloaded file replaces the internal theme file entirely (no merge).

Internal theme.json reference

The default internal theme file is published at theme.json. You can use it as a starting point to build a replacement or a partial override object.

The key sections are "light" and "dark", each containing:

KeyWhat it controls
primary.mainPrimary accent — buttons, links, focus rings
navigation.backgroundSidebar background
navigation.indicatorSelection line under the active sidebar item
navigation.color / navigation.selectedColorSidebar item text, unselected / selected
navigation.navItem.hoverBackgroundSidebar item background on hover
navigation.submenu.backgroundExpanded sidebar submenu background
tabbar.indicatorSelection line under the active tab
pageThemesTop bar gradient colors on entity pages
bursts.backgroundColor / bursts.gradient.linearDecorative banners on landing pages
backgroundPage background and card/paper color
statusTag and alert colors (ok, warning, error, running, pending, aborted)
link / linkHoverInline link color
pinSidebarButton.icon / pinSidebarButton.backgroundSidebar pin/unpin button

This table covers the raw theme.json keys read by THEME_CUSTOM_JSON/THEME_DOWNLOAD_URL. The same keys, under the same names, are also reachable through the structured app.branding.theme.* config — see the full value dump in Simple Branding if you'd rather configure via app-config than raw JSON.

Troubleshooting

SymptomCauseFix
Palette stays default even with THEME_CUSTOM_JSON setMalformed JSONValidate with jq before deploying (see above)
Some colors applied, others didn'tTHEME_MERGE_JSON=false with an incomplete JSON objectUse the default merge behavior (any value other than "false") so unspecified tokens keep their default
Favicon doesn't changeTHEME_FAV_ICON URL returns a non-2xx statusConfirm the URL returns 200 before setting it