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
Section titled “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
Section titled “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
Section titled “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
Section titled “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
Section titled “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
Section titled “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
Section titled “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
Section titled “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 |