Skip to content

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:

  1. CUSTOM_WHITELABEL variant payload for the requesting org (Unleash strategy matches orgId).
  2. Environment-variable defaults baked into the install (NEXT_PUBLIC_DEFAULT_* for the frontend, DEFAULT_APP_NAME for the backend).
  3. 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 varRequiredNotes
NEXT_PUBLIC_DEFAULT_APP_NAMEYesProduct name in the UI and API docs
NEXT_PUBLIC_DEFAULT_LOGO_URLYesHTTPS URL to the logo image
NEXT_PUBLIC_DEFAULT_FAVICON_URLYesHTTPS URL to the favicon
NEXT_PUBLIC_DEFAULT_PRIVACY_URLYesHTTPS URL to the privacy policy
NEXT_PUBLIC_DEFAULT_TERMS_OF_USE_URLYesHTTPS URL to the terms of use
NEXT_PUBLIC_DEFAULT_TERMS_OF_SERVICE_URLYesHTTPS URL to the terms of service
NEXT_PUBLIC_DEFAULT_MARKETING_WEBSITE_CONTACT_URLYesHTTPS URL for the “Contact us” link
NEXT_PUBLIC_DEFAULT_PUBLIC_DOCS_URLYesHTTPS URL to your public docs site
NEXT_PUBLIC_DEFAULT_DISCLAIMERNoFooter disclaimer text
NEXT_PUBLIC_DEFAULT_DISCLAIMER_POLICY_URLNoHTTPS URL linked from the disclaimer
DEFAULT_APP_NAMEYesBackend 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.

FieldTypePurpose
appNamestringOverrides product name in UI, OpenAPI docs, and server-rendered titles
logoUrlHTTPS URLTop-nav and login logo
faviconUrlHTTPS URLBrowser tab favicon
disclaimerstringFooter disclaimer text
dashboardDisclaimerstringDashboard-specific disclaimer; falls back to disclaimer when omitted
disclaimerPolicyUrlHTTPS URLLink rendered from the disclaimer
privacyUrlHTTPS URLPrivacy policy link
termsOfUseUrlHTTPS URLTerms of use link
termsOfServiceUrlHTTPS URLTerms of service link
marketingWebsiteContactUrlHTTPS URL”Contact us” link
publicDocsUrlHTTPS URLPublic 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

  1. Open the Unleash admin UI at unleash.<your-base-domain> and sign in with an admin account.

  2. Find the CUSTOM_WHITELABEL flag. If it does not yet exist, kindo install --step post-install will have created it on the next run; you can also create it manually as a Release flag.

  3. 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 type string — the platform parses it as JSON itself.

  4. Add a strategy for the variant that matches the target org. Recommended approach: use the orgId context field via a Gradual rollout strategy with a constraint of orgId IN [<org-uuid>]. Set rollout to 100% so every request from that org hits the variant deterministically.

  5. Enable the flag in the environment you are targeting (typically production).

  6. 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 next app 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 the FeatureFlagWrapper.
  • Backend — the api service resolves appName from 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

SymptomLikely causeFix
Branding shows Kindo defaults even with the flag enabledDEFAULT_APP_NAME or NEXT_PUBLIC_DEFAULT_APP_NAME env vars are unsetSet the env vars on the api and next deployments and restart the pods
Logo or favicon does not loadAsset URL is http:// instead of https://Re-host the asset over HTTPS — only HTTPS URLs are accepted
Variant payload is silently ignoredInvalid JSON or schema validation failureCheck api and next pod logs for Invalid CUSTOM_WHITELABEL payload or Failed to parse warnings
Wrong org sees the brandingVariant strategy is missing the orgId constraintAdd a Gradual rollout strategy with an orgId IN [...] constraint and 100% rollout
Branding flickers between defaults and override on loadUnleash Edge cache miss on first requestExpected during the first few requests after a payload change — resolves once Edge warms up