TENANCY FEATURE
Multi-Layer Tenancy
Four independent layers compose into one TenantContext, so tenant identity, locale, theme, and environment stay explicit instead of collapsing into one switch.
Organization → Locale → Theme → Environment. Each layer answers a different product question, and together they form one explicit TenantContext.
This page is not about stacking abstractions for their own sake. It shows how tenancy stops being mysterious when each decision is isolated, named, and resolved once.
Resolve organization first
The runtime must decide whose request this is before any other tenant-aware behavior makes sense.
Derive locale and theme independently
Language and branding are separate concerns. One can change without forcing the other to be hard-wired.
Keep environment as a layer, not a hidden global
Production, staging, or preview behavior should be explicit in the tenant context instead of leaking through conditionals.
OrganizationLayer
Identifies which tenant (organization) owns this request.
LocaleLayer
Determines the preferred language for this tenant request.
ThemeLayer
Loads per-tenant branding — colors, fonts, feature flags.
EnvironmentLayer
Identifies the deployment stage for this tenant.
Organization × Theme Matrix
Once the layers are composed, each tenant gets a consistent final surface without rebuilding the rules in handlers or templates.
| Tenant | Organization | Theme | Locale | Color |
|---|---|---|---|---|
| Acme Corp | acme |
acme-theme |
en |
#1e40af |
| Globex Inc | globex |
globex-theme |
de |
#166534 |
| Initech Ltd | initech |
initech-theme |
uk |
#c2410c |
MultilayerTenantResolver internals
Each LayerDefinition pairs a layer type with a resolver strategy. The resolver runs all strategies, builds each layer independently, then merges them into a single immutable TenantContext.
The important architectural habit is this: downstream code reads the composed context, not the mechanics of how each layer was produced. Layers are resolved in order, but remain conceptually independent.