Configuring Multi-Brand Branding and Theming in authentik: Complete Implementation Guide
authentik supports multi-brand deployments where each domain can display its own logo, title, favicon, custom CSS, and map tiles through a combination of Python middleware, database models, and Lit-based web components.
Multi-brand configuration in authentik allows organizations to host multiple tenants from a single instance, with each tenant presenting a fully customized visual identity. This guide examines the complete implementation across the Python backend and TypeScript frontend, referencing actual source paths and providing production-ready code examples.
Understanding the Multi-Brand Architecture
The multi-brand system spans four layers: data persistence, API exposure, request-time resolution, and UI rendering. Each layer is engineered to keep brand data synchronized from database to pixel.
Data Model and Storage
Brand-specific attributes reside in the database through the Brand model defined in [authentik/brands/models.py](https://github.com/goauthentik/authentik/blob/main/authentik/brands/models.py#L40):
branding_title— Display name shown throughout the UIbranding_logo— URL or path to the tenant's logobranding_favicon— Icon displayed in browser tabsbranding_custom_css— Raw CSS injected into every pagebranding_map_tiles— Custom tile server URL for map components
The model also includes a domain field that determines which brand activates for incoming requests.
API Layer
REST operations are exposed via [authentik/brands/api.py](https://github.com/goauthentik/authentik/blob/main/authentik/brands/api.py#L41), where BrandSerializer and BrandViewSet map the model to authentik's auto-generated OpenAPI schema. This enables both programmatic management and the built-in admin interface.
How Brand Resolution Works at Runtime
Brand selection happens on every request through middleware, then propagates through the entire request lifecycle.
Step 1: Middleware Host Matching
[authentik/brands/middleware.py](https://github.com/goauthentik/authentik/blob/main/authentik/brands/middleware.py#L12) implements BrandMiddleware, which:
- Extracts the hostname from the incoming request
- Queries for a
Brandwhosedomainmatches exactly - Falls back to the default brand if no match exists
- Attaches the resolved brand to
request.brand
# Conceptual flow from authentik/brands/middleware.py
class BrandMiddleware:
def __call__(self, request):
host = request.get_host().split(":")[0]
brand = Brand.objects.filter(domain=host).first()
request.brand = brand or Brand.objects.get_default()
return self.get_response(request)
Step 2: Default Flow Redirection
The resolved brand drives navigation behavior. [authentik/core/views/interface.py](https://github.com/goauthentik/authentik/blob/main/authentik/core/views/interface.py#L71) contains BrandDefaultRedirectView, which reads request.brand to determine where to send users accessing the root URL:
# From authentik/core/views/interface.py
class BrandDefaultRedirectView(InterfaceView):
def get(self, request, *args, **kwargs):
flow = request.brand.branding_default_flow
return HttpResponseRedirect(flow.get_absolute_url())
Frontend Branding with Lit Context
The authentik web UI uses Lit's context system to make brand data available globally without prop drilling.
The BrandingMixin Pattern
[web/src/elements/mixins/branding.ts](https://github.com/goauthentik/authentik/blob/main/web/src/elements/mixins/branding.ts) defines BrandingMixin, which:
- Creates
BrandingContextusing Lit'screateContext - Exposes typed getters:
brandingTitle,brandingLogo,brandingFavicon,brandingCustomCss,brandingMapTiles - Can be applied to any LitElement via
WithBrandConfig
Components consuming this mixin automatically re-render when brand data changes.
Component Usage Example
[web/src/components/ak-page-navbar.ts](https://github.com/goauthentik/authentik/blob/main/web/src/components/ak-page-navbar.ts#L97) demonstrates real-world consumption:
import { WithBrandConfig } from "#elements/mixins/branding";
export class AkPageNavbar extends WithBrandConfig(LitElement) {
render() {
return html`
<nav>
<img src="${this.brandingLogo}" alt="${this.brandingTitle}" />
<span>${this.brandingTitle}</span>
</nav>
`;
}
}
Custom CSS Injection
The branding_custom_css field is automatically converted to a stylesheet and applied to the root element. For programmatic injection in custom components:
import { createStyleSheetUnsafe } from "lit";
if (this.brand?.brandingCustomCss) {
const sheet = createStyleSheetUnsafe(this.brand.brandingCustomCss);
this.shadowRoot?.adoptedStyleSheets.push(sheet);
}
Creating and Managing Brands
Via REST API (TypeScript)
Use the generated @goauthentik/api client for programmatic brand creation:
import { BrandApi, BrandRequest } from "@goauthentik/api";
const api = new BrandApi();
const newBrand: BrandRequest = {
domain: "example.org",
branding_title: "Example Corp",
branding_logo: "/static/custom/logo.svg",
branding_favicon: "/static/custom/favicon.ico",
branding_custom_css: `
:root {
--pf-global--primary-color--100: #0066cc;
}
.pf-c-button { border-radius: 4px; }
`,
branding_map_tiles: "https://my.tileserver.org/{z}/{x}/{y}.png",
};
await api.brandsCreate(newBrand);
Via Admin UI
The brand management interface lives at [web/src/admin/brands/BrandForm.ts](https://github.com/goauthentik/authentik/blob/main/web/src/admin/brands/BrandForm.ts), providing fields for all branding attributes including file uploads for logos and favicons.
Brand-Aware Custom Components
When building extensions that need brand data, apply the mixin pattern:
import { WithBrandConfig } from "#elements/mixins/branding";
import { html, LitElement } from "lit";
export class MyCustomHeader extends WithBrandConfig(LitElement) {
render() {
const cssUrl = this.brandingCustomCss
? undefined
: "/static/default-theme.css";
return html`
<header style="background: var(--brand-color)">
<img
src="${this.brandingLogo}"
alt="${this.brandingTitle}"
@error="${() => this.useFallbackLogo()}"
/>
<h1>${this.brandingTitle}</h1>
</header>
`;
}
private useFallbackLogo() {
// Handle missing or broken logo assets
}
}
Fallback Configuration
When no brand matches or the default brand lacks configuration, [web/src/common/ui/config.ts](https://github.com/goauthentik/authentik/blob/main/web/src/common/ui/config.ts) supplies baseline values. This ensures the UI never renders without valid branding data.
Summary
- Multi-brand branding in authentik works through domain-based
Brandmodel resolution, middleware attachment torequest.brand, and Lit context propagation to all UI components - Key files: [
authentik/brands/models.py](https://github.com/goauthentik/authentik/blob/main/authentik/brands/models.py#L40) (storage), [middleware.py](https://github.com/goauthentik/authentik/blob/main/authentik/brands/middleware.py#L12) (resolution), [web/src/elements/mixins/branding.ts](https://github.com/goauthentik/authentik/blob/main/web/src/elements/mixins/branding.ts) (UI context) - Theming mechanisms include per-brand CSS injection and configurable map tile servers
- Management options span REST API with
@goauthentik/apiand the built-in admin interface at [BrandForm.ts](https://github.com/goauthentik/authentik/blob/main/web/src/admin/brands/BrandForm.ts)
Frequently Asked Questions
What happens if no brand matches the incoming domain?
The middleware falls back to the default brand configured in the system. Default values from [web/src/common/ui/config.ts](https://github.com/goauthentik/authentik/blob/main/web/src/common/ui/config.ts) ensure the UI remains functional even without custom branding.
Can I use environment variables instead of the database for branding?
No. authentik's multi-brand system requires database storage to support runtime domain resolution and per-tenant isolation. The Brand model is the single source of truth for all branding attributes.
How do I apply brand-specific CSS to Shadow DOM components?
The branding_custom_css stylesheet is applied at the application root. For Shadow DOM isolation, components using WithBrandConfig can manually adopt the stylesheet using createStyleSheetUnsafe and adoptedStyleSheets, or inherit CSS custom properties defined in the global stylesheet.
Are map tiles only used for geographic visualizations?
Yes. The branding_map_tiles field specifically configures tile server URLs for map components. This allows different tenants to use custom basemaps—corporate styles, satellite imagery, or regional tile servers—wherever authentik renders location data.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →