Customize Branding (Whitelabel)
Self-Managed Kindo supports per-org whitelabel branding — your own app name, logo, favicon, footer disclaimer, and policy URLs — without rebuilding the image. Branding is delivered through an Unleash feature flag called CUSTOM_WHITELABEL whose variant payload carries the JSON config the frontend and backend read to brand the install.
This page covers what you can override, how the flag and env-var fallbacks compose, and how to roll branding out per-org or globally.
How branding resolves
Branding is resolved in this order, first match wins:
CUSTOM_WHITELABELvariant payload for the requesting org (Unleash strategy matchesorgId).- Environment-variable defaults baked into the install (
NEXT_PUBLIC_DEFAULT_*for the frontend,DEFAULT_APP_NAMEfor the backend). - Built-in Kindo defaults if the env var is missing for an optional field.
If a payload field is omitted, that single field falls back to the env-var default — you can override only the fields you need.
Environment-variable defaults
Set these on the next deployment to define your global defaults. They flow into the runtime config injected at startup (window.__ENV) and are read by the frontend whenever the CUSTOM_WHITELABEL flag is off or the payload omits a field.
| Env var | Required | Notes |
|---|---|---|
NEXT_PUBLIC_DEFAULT_APP_NAME | Yes | Product name in the UI and API docs |
NEXT_PUBLIC_DEFAULT_LOGO_URL | Yes | HTTPS URL to the logo image |
NEXT_PUBLIC_DEFAULT_FAVICON_URL | Yes | HTTPS URL to the favicon |
NEXT_PUBLIC_DEFAULT_PRIVACY_URL | Yes | HTTPS URL to the privacy policy |
NEXT_PUBLIC_DEFAULT_TERMS_OF_USE_URL | Yes | HTTPS URL to the terms of use |
NEXT_PUBLIC_DEFAULT_TERMS_OF_SERVICE_URL | Yes | HTTPS URL to the terms of service |
NEXT_PUBLIC_DEFAULT_MARKETING_WEBSITE_CONTACT_URL | Yes | HTTPS URL for the “Contact us” link |
NEXT_PUBLIC_DEFAULT_PUBLIC_DOCS_URL | Yes | HTTPS URL to your public docs site |
NEXT_PUBLIC_DEFAULT_DISCLAIMER | No | Footer disclaimer text |
NEXT_PUBLIC_DEFAULT_DISCLAIMER_POLICY_URL | No | HTTPS URL linked from the disclaimer |
DEFAULT_APP_NAME | Yes | Backend equivalent of NEXT_PUBLIC_DEFAULT_APP_NAME; used in OpenAPI doc titles and server-rendered surfaces |
All URL fields are validated as HTTPS — http:// values are rejected and the field falls back to the default.
Variant payload schema
When the CUSTOM_WHITELABEL flag is enabled and the active variant is matched, its payload is parsed as JSON against this schema. All fields are optional; omit any field to inherit the env-var default.
| Field | Type | Purpose |
|---|---|---|
appName | string | Overrides product name in UI, OpenAPI docs, and server-rendered titles |
logoUrl | HTTPS URL | Top-nav and login logo |
faviconUrl | HTTPS URL | Browser tab favicon |
disclaimer | string | Footer disclaimer text |
dashboardDisclaimer | string | Dashboard-specific disclaimer; falls back to disclaimer when omitted |
disclaimerPolicyUrl | HTTPS URL | Link rendered from the disclaimer |
privacyUrl | HTTPS URL | Privacy policy link |
termsOfUseUrl | HTTPS URL | Terms of use link |
termsOfServiceUrl | HTTPS URL | Terms of service link |
marketingWebsiteContactUrl | HTTPS URL | ”Contact us” link |
publicDocsUrl | HTTPS URL | Public docs link |
Validation is strict — an invalid payload (bad JSON, non-HTTPS URL, wrong type) is logged as a warning and the entire payload is discarded for that request, falling back to env-var defaults. The flag does not break the page.
Example payload
{ "appName": "Acme AI", "logoUrl": "https://cdn.acme.example.com/branding/logo.svg", "faviconUrl": "https://cdn.acme.example.com/branding/favicon.png", "disclaimer": "Acme AI is for internal use only. Do not paste customer PII.", "disclaimerPolicyUrl": "https://acme.example.com/legal/ai-policy", "privacyUrl": "https://acme.example.com/legal/privacy", "termsOfUseUrl": "https://acme.example.com/legal/terms", "termsOfServiceUrl": "https://acme.example.com/legal/tos", "marketingWebsiteContactUrl": "https://acme.example.com/contact", "publicDocsUrl": "https://docs.acme.example.com"}Configure branding for an org
-
Open the Unleash admin UI at
unleash.<your-base-domain>and sign in with an admin account. -
Find the
CUSTOM_WHITELABELflag. If it does not yet exist,kindo install --step post-installwill have created it on the next run; you can also create it manually as a Release flag. -
Add a variant named for the target org (e.g.
acme-prod). Paste the JSON payload (see the example above) into the variant’s Payload (string) field. Use payload typestring— the platform parses it as JSON itself. -
Add a strategy for the variant that matches the target org. Recommended approach: use the
orgIdcontext field via a Gradual rollout strategy with a constraint oforgId IN [<org-uuid>]. Set rollout to 100% so every request from that org hits the variant deterministically. -
Enable the flag in the environment you are targeting (typically
production). -
Verify. Sign in to Kindo as a user of the target org and confirm the app name, logo, favicon, and disclaimer match the payload. Sign in as a user of any other org and confirm they still see the env-var defaults.
Global branding without per-org overrides
If every org in your install should see the same branding, you do not need to touch CUSTOM_WHITELABEL at all — set the NEXT_PUBLIC_DEFAULT_* env vars (and DEFAULT_APP_NAME on the backend) to your branding values and leave the flag off. The frontend and backend will use those everywhere.
How the frontend and backend consume the variant
- Frontend — the
nextapp reads the variant via the Unleash SDK on every render. Pre-auth surfaces (login, password reset) work because the flag is registered as a global context-aware flag in theFeatureFlagWrapper. - Backend — the
apiservice resolvesappNamefrom the variant at app construction time so OpenAPI doc titles and server-rendered text use the right product name. Other branding fields (logo, disclaimers, policy URLs) are frontend-only. - Task workers and streaming paths — server-rendered branding (e.g. in streamed responses) goes through the same backend resolver.
Troubleshooting
| Symptom | Likely cause | Fix |
|---|---|---|
| Branding shows Kindo defaults even with the flag enabled | DEFAULT_APP_NAME or NEXT_PUBLIC_DEFAULT_APP_NAME env vars are unset | Set the env vars on the api and next deployments and restart the pods |
| Logo or favicon does not load | Asset URL is http:// instead of https:// | Re-host the asset over HTTPS — only HTTPS URLs are accepted |
| Variant payload is silently ignored | Invalid JSON or schema validation failure | Check api and next pod logs for Invalid CUSTOM_WHITELABEL payload or Failed to parse warnings |
| Wrong org sees the branding | Variant strategy is missing the orgId constraint | Add a Gradual rollout strategy with an orgId IN [...] constraint and 100% rollout |
| Branding flickers between defaults and override on load | Unleash Edge cache miss on first request | Expected during the first few requests after a payload change — resolves once Edge warms up |