Multi-Environment Support¶
Multi-environment support enables you to manage feature flags across different deployment environments (development, staging, production) with environment-specific configurations and hierarchical inheritance.
What is Multi-Environment Support?¶
Multi-environment support allows you to:
Define separate environments with their own flag configurations
Inherit flag values from parent environments to reduce duplication
Override specific flags per environment without affecting others
Automatically detect the current environment from requests
Promote flags between environments in a controlled workflow
This is essential for teams that need different feature flag behavior across their deployment pipeline.
Use Cases¶
Development/Staging/Production Pipeline
Enable features in development and staging for testing before production rollout:
production (base)
|
+-- staging (inherits from production, enables beta features)
| |
| +-- development (inherits from staging, enables experimental features)
Regional Deployments
Customize features for different geographic regions:
global (base)
|
+-- us (US-specific features)
|
+-- eu (EU-specific features, GDPR compliance)
|
+-- asia (Asia-specific features)
Feature Branch Testing
Isolate flag configurations for feature branch deployments:
staging
|
+-- feature-xyz (isolated environment for feature testing)
Core Concepts¶
Environment Model¶
The Environment model represents a deployment environment with optional
inheritance from a parent environment.
Field |
Type |
Description |
|---|---|---|
|
|
Human-readable display name (e.g., “Production”, “Staging”) |
|
|
URL-safe unique identifier (e.g., “production”, “staging”) |
|
|
Optional description of the environment’s purpose |
|
|
Reference to parent environment for inheritance |
|
|
Environment-specific settings stored as JSON |
|
|
Whether this environment is active for flag evaluation |
|
|
The parent Environment object (populated via relationship) |
|
|
Child environments that inherit from this environment |
EnvironmentFlag Model¶
The EnvironmentFlag model stores per-environment flag overrides. Values set
to None indicate that the flag should inherit from the base flag or parent
environment.
Field |
Type |
Description |
|---|---|---|
|
|
Reference to the environment this override applies to |
|
|
Reference to the base feature flag being overridden |
|
|
Override enabled state ( |
|
|
Override rollout percentage ( |
|
|
Override targeting rules as JSON ( |
|
|
Override variants as JSON ( |
Environment Inheritance Chain¶
When evaluating a flag for a specific environment, the resolver walks the inheritance chain from the most specific environment (child) up to the root (parent with no parent). The first override found is applied.
Request for "staging" environment:
1. Check if staging has override for flag -> YES -> use staging's value
2. Check if dev (staging's parent) has override -> skip (found in step 1)
3. Check base flag configuration -> skip (found in step 1)
Request for "feature-branch" environment:
1. Check if feature-branch has override -> NO
2. Check if staging (feature-branch's parent) has override -> YES -> use staging's value
3. Check base flag configuration -> skip (found in step 2)
This allows you to define flag values at the appropriate level and have them automatically propagate to child environments.
Configuration¶
FeatureFlagsConfig Settings¶
Configure multi-environment support in your FeatureFlagsConfig:
from litestar_flags import FeatureFlagsConfig
config = FeatureFlagsConfig(
backend="database",
connection_string="postgresql+asyncpg://user:pass@localhost/db",
# Environment settings
default_environment="production",
enable_environment_inheritance=True,
enable_environment_middleware=True,
environment_header="X-Environment",
environment_query_param="env",
allowed_environments=["production", "staging", "development"],
)
Setting |
Description |
|---|---|
|
Default environment slug when none is detected from request. Must be alphanumeric with hyphens or underscores. |
|
Whether child environments inherit flag values from parents.
Defaults to |
|
Whether to enable automatic environment detection from requests. |
|
HTTP header name for environment detection (default: |
|
Query parameter name for environment detection (default: |
|
Optional list of allowed environment slugs. Requests with environments
not in this list fall back to |
Environment Middleware Setup¶
The EnvironmentMiddleware automatically extracts the environment from
incoming requests and stores it in the request scope for flag evaluation.
Detection Priority:
HTTP header (configurable via
environment_header)Query parameter (configurable via
environment_query_param)Default environment from config (
default_environment)
from litestar import Litestar
from litestar_flags import FeatureFlagsConfig, FeatureFlagsPlugin
config = FeatureFlagsConfig(
backend="memory",
enable_environment_middleware=True,
environment_header="X-Environment",
environment_query_param="env",
default_environment="production",
allowed_environments=["production", "staging", "development"],
)
app = Litestar(
route_handlers=[...],
plugins=[FeatureFlagsPlugin(config=config)],
)
Requests are then processed with environment detection:
# Environment from header
curl -H "X-Environment: staging" https://api.example.com/feature
# Environment from query param
curl "https://api.example.com/feature?env=development"
# Falls back to default_environment
curl https://api.example.com/feature
Accessing the Request Environment¶
Use get_request_environment() to retrieve the detected environment:
from litestar import get
from litestar.connection import Request
from litestar_flags import get_request_environment
@get("/debug")
async def debug_environment(request: Request) -> dict:
environment = get_request_environment(request.scope)
return {"current_environment": environment}
Usage Examples¶
Creating Environments with Inheritance¶
Create a hierarchical environment structure where staging inherits from production, and development inherits from staging:
from litestar_flags.models.environment import Environment
# Create the root production environment
production = Environment(
name="Production",
slug="production",
description="Live production environment",
settings={"require_approval": True},
)
production = await storage.create_environment(production)
# Create staging that inherits from production
staging = Environment(
name="Staging",
slug="staging",
description="Pre-production testing environment",
parent_id=production.id,
settings={"require_approval": False},
)
staging = await storage.create_environment(staging)
# Create development that inherits from staging
development = Environment(
name="Development",
slug="development",
description="Local development environment",
parent_id=staging.id,
settings={"debug_mode": True},
)
development = await storage.create_environment(development)
Setting Per-Environment Flag Values¶
Override flag configurations for specific environments:
from litestar_flags.models.environment_flag import EnvironmentFlag
# Get the base flag
flag = await storage.get_flag("new-checkout-flow")
# Enable with 100% rollout in staging for testing
staging_override = EnvironmentFlag(
environment_id=staging.id,
flag_id=flag.id,
enabled=True,
percentage=100.0,
)
await storage.create_environment_flag(staging_override)
# Enable with 10% rollout in production for gradual release
production_override = EnvironmentFlag(
environment_id=production.id,
flag_id=flag.id,
enabled=True,
percentage=10.0,
)
await storage.create_environment_flag(production_override)
# Development inherits from staging (no override needed)
# It will automatically get 100% rollout
Evaluating Flags in Environment Context¶
The EnvironmentResolver handles flag evaluation with environment overrides:
from litestar_flags.environment import EnvironmentResolver
# Create a resolver with your storage backend
resolver = EnvironmentResolver(storage)
# Get the base flag
base_flag = await storage.get_flag("new-checkout-flow")
# Resolve for staging environment
staging_flag = await resolver.resolve_flag_for_environment(
flag=base_flag,
environment_slug="staging",
)
print(f"Staging rollout: {staging_flag.default_enabled}") # True
# Resolve for production environment
production_flag = await resolver.resolve_flag_for_environment(
flag=base_flag,
environment_slug="production",
)
print(f"Production rollout: {production_flag.default_enabled}") # True (10%)
Getting the Environment Inheritance Chain¶
Inspect the inheritance chain for debugging:
from litestar_flags.environment import EnvironmentResolver
resolver = EnvironmentResolver(storage)
# Get the full inheritance chain for development
chain = await resolver.get_environment_chain("development")
for env in chain:
print(f" {env.slug}: {env.name}")
# Output:
# development: Development
# staging: Staging
# production: Production
Promoting Flags Between Environments¶
Manually copy flag configuration from one environment to another:
async def promote_flag(
storage,
flag_key: str,
from_env: str,
to_env: str,
) -> None:
"""Promote a flag configuration from one environment to another."""
# Get the source and target environments
source = await storage.get_environment(from_env)
target = await storage.get_environment(to_env)
# Get the flag
flag = await storage.get_flag(flag_key)
# Get the source environment's override
source_override = await storage.get_environment_flag(
env_id=source.id,
flag_id=flag.id,
)
if source_override is None:
raise ValueError(f"No override for {flag_key} in {from_env}")
# Check if target already has an override
existing = await storage.get_environment_flag(
env_id=target.id,
flag_id=flag.id,
)
if existing:
# Update existing override
existing.enabled = source_override.enabled
existing.percentage = source_override.percentage
existing.rules = source_override.rules
existing.variants = source_override.variants
await storage.update_environment_flag(existing)
else:
# Create new override
new_override = EnvironmentFlag(
environment_id=target.id,
flag_id=flag.id,
enabled=source_override.enabled,
percentage=source_override.percentage,
rules=source_override.rules,
variants=source_override.variants,
)
await storage.create_environment_flag(new_override)
# Example: Promote from staging to production
await promote_flag(storage, "new-checkout-flow", "staging", "production")
API Reference¶
EnvironmentResolver Methods¶
- class EnvironmentResolver(storage)¶
Resolves feature flags with environment-specific overrides.
- async get_environment(slug) Environment | None¶
Get an environment by slug.
- Parameters:
slug – The unique URL-safe identifier for the environment.
- Returns:
The Environment if found, None otherwise.
- async get_environment_chain(slug) list[Environment]¶
Get the inheritance chain for an environment from child to root.
Walks the parent chain starting from the specified environment up to the root (an environment with no parent).
- Parameters:
slug – The slug of the environment to start from.
- Returns:
List of environments ordered from child (most specific) to root (least specific).
- Raises:
CircularEnvironmentInheritanceError – If a circular reference is detected in the inheritance chain.
- async get_effective_environment_flag(flag_id, environment_slug) EnvironmentFlag | None¶
Get the effective environment flag by walking the inheritance chain.
Returns the first EnvironmentFlag override found in the chain.
- Parameters:
flag_id – The UUID of the feature flag.
environment_slug – The slug of the environment to start from.
- Returns:
The first EnvironmentFlag found, or None if no override exists.
- Raises:
CircularEnvironmentInheritanceError – If a circular reference is detected.
- async resolve_flag_for_environment(flag, environment_slug) FeatureFlag¶
Resolve a flag with environment overrides applied.
- Parameters:
flag – The base feature flag to resolve.
environment_slug – The slug of the environment to resolve for.
- Returns:
A new FeatureFlag instance with environment overrides applied.
- Raises:
CircularEnvironmentInheritanceError – If a circular reference is detected.
Storage Backend Environment Methods¶
The StorageBackend protocol includes the following environment-related methods:
Environment Management:
get_environment(slug)- Retrieve an environment by slugget_environment_by_id(env_id)- Retrieve an environment by UUIDget_all_environments()- Retrieve all environmentsget_child_environments(parent_id)- Get child environments of a parentcreate_environment(env)- Create a new environmentupdate_environment(env)- Update an existing environmentdelete_environment(slug)- Delete an environment by slug
EnvironmentFlag Management:
get_environment_flag(env_id, flag_id)- Get specific environment-flag configget_environment_flags(env_id)- Get all flag configs for an environmentget_flag_environments(flag_id)- Get all environment configs for a flagcreate_environment_flag(env_flag)- Create environment-specific overrideupdate_environment_flag(env_flag)- Update environment-specific overridedelete_environment_flag(env_id, flag_id)- Delete environment override
Exception Classes¶
- exception CircularEnvironmentInheritanceError¶
Raised when circular environment inheritance is detected.
This error occurs when traversing the environment inheritance chain and an environment references itself either directly or through a chain of parent environments.
Best Practices¶
Environment Naming Conventions¶
Use consistent, descriptive slugs for your environments:
Use lowercase letters, numbers, hyphens, and underscores
Start with a letter or number
Keep names short but descriptive
Use a consistent prefix for related environments
Good:
- production, staging, development
- prod, stage, dev
- us-east, us-west, eu-central
- feature-checkout, feature-payment
Avoid:
- PRODUCTION (use lowercase)
- my production env (no spaces)
- -staging (don't start with hyphen)
Inheritance Patterns¶
Linear Pipeline (Recommended for most teams):
production
|
+-- staging
|
+-- development
All changes flow through the pipeline, with each environment inheriting from its more production-like parent.
Feature Branch Pattern:
staging
|
+-- feature-xyz
|
+-- feature-abc
Feature branches inherit from staging to get baseline behavior while allowing isolated testing.
Regional Pattern:
global
|
+-- us
| +-- us-east
| +-- us-west
|
+-- eu
+-- eu-central
+-- eu-west
Regional environments inherit global defaults while allowing geo-specific overrides.
Promotion Workflows¶
Follow a consistent process for promoting flag configurations:
Test in development - Verify flag behavior with development overrides
Validate in staging - Test with production-like data and load
Gradual production rollout - Start with low percentage, increase over time
Monitor and iterate - Watch metrics before increasing rollout
Example promotion flow:
# 1. Enable 100% in development for testing
await set_environment_flag("new-feature", "development", enabled=True, percentage=100)
# 2. After dev testing, enable in staging
await set_environment_flag("new-feature", "staging", enabled=True, percentage=100)
# 3. After staging validation, gradual production rollout
await set_environment_flag("new-feature", "production", enabled=True, percentage=5)
# ... monitor metrics ...
await set_environment_flag("new-feature", "production", percentage=25)
# ... monitor metrics ...
await set_environment_flag("new-feature", "production", percentage=100)
Security Considerations¶
Restrict environment headers in production - Use
allowed_environmentsto prevent arbitrary environment injectionDisable query param detection in production - Set
environment_query_param=Noneto prevent environment override via URLAudit environment changes - Log all environment and environment flag modifications
Use separate credentials - Different database/Redis credentials per environment
# Production-safe configuration
config = FeatureFlagsConfig(
backend="database",
connection_string=os.environ["DATABASE_URL"],
# Lock down environment detection
default_environment="production",
enable_environment_middleware=True,
environment_header="X-Environment",
environment_query_param=None, # Disable query param in production
allowed_environments=["production"], # Only allow production
)