Skip to content

Configuration Layers

Manager uses a three-layer configuration system that provides flexible settings inheritance from project defaults down to what one tenant sees in one application.

The Three Layers

┌─────────────────────────────────────┐
│        Project Settings             │  ← Most General
│   (Defaults for all tenants/apps)   │
└─────────────────────────────────────┘
              ↓ inherits
┌─────────────────────────────────────┐
│         Tenant Settings             │  ← Middle Layer
│ (Defaults for this customer org)    │
└─────────────────────────────────────┘
              ↓ inherits
┌─────────────────────────────────────┐
│  Tenant × Application Settings      │  ← Most Specific (leaf)
│  (Tenant2Application: one customer  │
│      org's use of one application)  │
└─────────────────────────────────────┘

The leaf layer is the Tenant2Application row

Since the Wave 3 re-architecture the cascade leaf is the Tenant2Application join — the per-(tenant, application) settings for one customer organization's use of one application. The Manager merges project → tenant → tenant2application, and returns 404 if the application is not enabled for the tenant. Config is fetched per (application, tenant): GET /api/frontend-config/:application_id/:tenant_id. See Multi-tenancy and the Migration Guide.

How Inheritance Works

When an application needs a setting for a tenant:

  1. Check Tenant × Application - Look in the Tenant2Application settings first
  2. Check Tenant - If not found, look in tenant settings
  3. Check Project - If still not found, look in project settings
  4. Use Default - If nowhere, use system default

Inheritance Example

The values below are illustrative custom key-value pairs (stored in the settings' KeyValuePairs):

Project: Customer Portal

json
{
  "theme": "light",
  "locale": "en",
  "features": ["basic-dashboard"]
}

Tenant: ACME Corp

json
{
  "theme": "dark",
  "support_email": "support@acme.com"
}

Tenant × Application: ACME Corp × Mobile App (the Tenant2Application leaf)

json
{
  "locale": "es",
  "features": ["chat"]
}

Effective Configuration for Mobile App as ACME Corp sees it:

json
{
  "theme": "dark", // from Tenant (overrides Project)
  "locale": "es", // from Tenant × Application (overrides Project)
  "support_email": "support@acme.com", // from Tenant
  "features": ["chat"] // from Tenant × Application (overrides Project)
}

Configuration Types

Backend Settings

Server-side configuration:

  • Modules - Which backend features are enabled
  • Key-Value Pairs - Custom parameters

Example:

json
{
  "EnabledModules": ["auth", "api", "analytics"],
  "KeyValuePairs": [
    { "Key": "api_version", "Value": "v2" },
    { "Key": "rate_limit", "Value": "1000" }
  ]
}

Frontend Settings

Client-side configuration:

  • Modules - UI components with versions
  • Themes - Available and default theme
  • Locales - Languages and default
  • Key-Value Pairs - Custom UI parameters

Example:

json
{
  "EnabledModules": ["dashboard@1.2.0"],
  "EnabledThemes": ["light", "dark"],
  "DefaultTheme": "light",
  "EnabledLocales": ["en", "es"],
  "DefaultLocale": "en"
}

When to Configure at Each Layer

Project Level

Use for:

  • Product line specific features
  • Common settings for related tenants/apps
  • Shared modules across all tenants
  • Environment-specific defaults (dev/qas/run environments)

Examples:

json
{
  "features": ["advanced-reporting", "api-access"],
  "EnabledModules": ["analytics", "webhooks"],
  "environment": "production"
}

Tenant Level

Use for:

  • Organization-wide branding (logo, colors)
  • Company-wide settings (support email, timezone)
  • Default language for tenant's apps
  • Security policies per customer

Examples:

json
{
  "theme": "corporate-blue",
  "locale": "en",
  "support_email": "support@company.com",
  "logo_url": "https://cdn.company.com/logo.png"
}

Application Level

Use for:

  • App-specific customizations
  • Overrides for special cases
  • Unique features per app
  • A/B testing variants

Examples:

json
{
  "locale": "fr", // This app uses French
  "theme": "custom-dark" // Custom theme
}

Viewing Effective Configuration

In Settings Editor

  1. Navigate to Application → Settings
  2. Look for Layer Viewer sidebar
  3. See configuration from all layers:
    • Values in bold = defined at current layer
    • Values in gray = inherited from parent
    • Highlighted = overriding parent value

Example Layer Viewer

┌─ Project (Customer Portal) ───────┐
│ theme: "light"                    │
│ locale: "en"                      │
└───────────────────────────────────┘

┌─ Tenant (ACME Corp) ──────────────┐
│ theme: "dark"  ← OVERRIDES        │
│ locale: "en"   ← inherited        │
│ support_email: "support@acme.com" │
└───────────────────────────────────┘

┌─ ACME Corp × Mobile App ──────────┐
│ theme: "dark"    ← inherited      │
│ locale: "es"     ← OVERRIDES      │
│ support_email: "..." ← inherited  │
└───────────────────────────────────┘

Override Strategies

The merge behavior depends on the field:

Replaced Fields

EnabledModules, EnabledThemes, and EnabledLocales are completely replaced when a lower layer defines a non-empty value:

Project: EnabledModules: ["reporting", "analytics"]Application: EnabledModules: ["admin-panel"]Result: Only ["admin-panel"] (completely replaced)

DefaultTheme and DefaultLocale are overridden whenever a lower layer sets a non-empty value.

Merged Fields

AvailableModules, AvailableThemes, and AvailableLocales are merged across layers (union with duplicates removed). KeyValuePairs are merged by key, with the more specific layer winning on conflicting keys.

Conditional Overrides

Override only in specific cases:

Example: Different theme for mobile app:

  • Project/Tenant: theme: "standard"
  • Mobile App: theme: "mobile-optimized"
  • Web App: (inherits "standard")

Best Practices

DRY (Don't Repeat Yourself)

[NO] Bad: Define same setting at every level

Project: support_email = "support@company.com"
Tenant: support_email = "support@company.com"
App 1: support_email = "support@company.com"
App 2: support_email = "support@company.com"

[OK] Good: Define once at project level

Project: support_email = "support@company.com"
(Tenant and Apps inherit)

Override Only When Necessary

Only set values at lower layers if they differ from parent:

Project: theme = "light"
Tenant: (no theme setting - inherits "light")
Special App: theme = "dark"  ← Only this app needs override

Document Overrides

When overriding, document why:

json
{
  "theme": "high-contrast",
  "_comment": "Accessibility app requires high contrast theme"
}

Test Configuration Changes

  1. Make changes in development/staging first
  2. View effective configuration
  3. Test application behavior
  4. Apply to production

Common Patterns

Multi-Environment Setup

Environments are a first-class entity (dev, qas, run) — not tenants. The same application row is deployed per environment; environment-specific settings live on the project (or are copied between environments), and per-customer overrides live on the tenant2application leaf:

Project: Customer Portal
  ├─ Environments:
  │   ├─ dev  (debug on, mock data)
  │   ├─ qas  (debug on)
  │   └─ run  (debug off)
  ├─ Applications: Portal   (one row, deployed per environment)
  └─ Tenants: Customer A, Customer B  (each enables Portal)

Never model an environment as a tenant. See Multi-tenancy.

Multi-Brand / Multi-Customer Setup

Use tenants for customer organizations (or brands) within a project, and put per-customer overrides on the enablement leaf:

Project: Multi-Brand Platform
  ├─ Tenant: Brand A
  │   └─ Brand A × App: { theme: "brand-a-blue", logo: "brand-a.png" }
  └─ Tenant: Brand B
      └─ Brand B × App: { theme: "brand-b-green", logo: "brand-b.png" }

Feature Flags

Control features at project level:

Project: Enterprise
  └─ settings: { features: ["advanced-analytics", "api-access", "sso"] }

Project: Basic
  └─ settings: { features: ["basic-analytics"] }

Troubleshooting

Setting Not Taking Effect

Check:

  1. Is it defined at a more specific layer?
  2. Is the setting key spelled correctly?
  3. View effective configuration to see what value is being used
  4. Is application caching the configuration?

Unexpected Value

Debug:

  1. Open Layer Viewer
  2. Check each layer from top to bottom
  3. Find where the value is defined
  4. Verify that's the intended behavior

Cannot Override Setting

Reason: Application may not support overriding that setting

Solution: Check application documentation for configurable settings

See Also