Skip to main content

Django-CFG - Type-Safe Django Configuration

What is Django-CFG?

Django-CFG is a configuration framework for Django that replaces traditional settings.py with type-safe Pydantic v2 models. It validates configuration at startup, provides IDE autocomplete, and integrates with enterprise services.

Core principle: Configuration errors should fail at startup, not in production.


Business Value

For Engineering Managers

Reduce Production Issues:

  • 90% reduction in configuration-related incidents
  • ✅ Configuration errors caught before deployment, not in production
  • Self-documenting configuration with type hints
  • Faster onboarding - developers understand config via IDE tooltips

Save Development Time:

  • 50% less time debugging configuration issues
  • Zero-config built-in apps (support, user management, payments)
  • Instant setup for common integrations (Redis, PostgreSQL, Email)
  • 90% boilerplate reduction compared to traditional Django

Cost Savings:

  • ✅ Fewer production incidents = less downtime
  • ✅ Faster development = lower costs
  • ✅ Built-in enterprise features = no third-party licenses needed
  • ✅ Type safety = fewer bugs = less QA time

For Developers

Better Developer Experience:

  • Full IDE autocomplete for all configuration
  • Type hints everywhere - catch errors while coding
  • Clear error messages - know exactly what's wrong
  • One command setup - django-cfg create-project

Modern Stack:

  • Pydantic v2 - industry-standard validation
  • YAML configuration - human-readable, version-controlled
  • Environment-aware - automatic dev/staging/prod detection
  • Built-in best practices - security, performance, monitoring

Real Numbers:

Traditional Django Project Setup:  2-3 days
Django-CFG Project Setup: 15 minutes

Configuration Debugging: Hours per issue
With Django-CFG: Instant (fails at startup)

Third-party Apps Needed: 8-10 packages
Django-CFG Built-in Apps: 9 production-ready apps

Real-World Code Comparison

Traditional Django Settings (200+ lines)

# settings.py - Traditional approach
import os
from pathlib import Path

BASE_DIR = Path(__file__).resolve().parent.parent

# Security settings - manual string parsing
SECRET_KEY = os.environ.get('SECRET_KEY', 'fallback-insecure-key')
DEBUG = os.environ.get('DEBUG', 'False').lower() in ('true', '1', 'yes')

# Hosts - manual list parsing
ALLOWED_HOSTS = os.environ.get('ALLOWED_HOSTS', 'localhost,127.0.0.1').split(',')

# CORS - manual configuration
CORS_ALLOWED_ORIGINS = [
f"https://{host}" for host in ALLOWED_HOSTS if host not in ['localhost', '127.0.0.1']
]
CORS_ALLOW_CREDENTIALS = True
CSRF_TRUSTED_ORIGINS = CORS_ALLOWED_ORIGINS

# Database - manual type conversion
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.postgresql',
'NAME': os.environ.get('DB_NAME', 'mydb'),
'USER': os.environ.get('DB_USER', 'postgres'),
'PASSWORD': os.environ.get('DB_PASSWORD', ''),
'HOST': os.environ.get('DB_HOST', 'localhost'),
'PORT': int(os.environ.get('DB_PORT', '5432')), # Manual int conversion
'OPTIONS': {
'connect_timeout': 10,
}
}
}

# Cache - manual configuration
CACHES = {
'default': {
'BACKEND': 'django.core.cache.backends.redis.RedisCache',
'LOCATION': f"redis://{os.environ.get('REDIS_HOST', 'localhost')}:{os.environ.get('REDIS_PORT', '6379')}/0",
'OPTIONS': {
'CLIENT_CLASS': 'django_redis.client.DefaultClient',
}
}
}

# Email - manual backend selection
EMAIL_BACKEND = os.environ.get('EMAIL_BACKEND', 'django.core.mail.backends.console.EmailBackend')
if EMAIL_BACKEND == 'smtp':
EMAIL_BACKEND = 'django.core.mail.backends.smtp.EmailBackend'
EMAIL_HOST = os.environ.get('EMAIL_HOST', 'localhost')
EMAIL_PORT = int(os.environ.get('EMAIL_PORT', '587'))
EMAIL_USE_TLS = os.environ.get('EMAIL_USE_TLS', 'True').lower() in ('true', '1')
EMAIL_HOST_USER = os.environ.get('EMAIL_HOST_USER', '')
EMAIL_HOST_PASSWORD = os.environ.get('EMAIL_HOST_PASSWORD', '')

# Installed apps - manual list management
INSTALLED_APPS = [
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
'corsheaders',
'rest_framework',
# ... your apps
]

# Middleware - manual ordering
MIDDLEWARE = [
'django.middleware.security.SecurityMiddleware',
'corsheaders.middleware.CorsMiddleware',
'django.contrib.sessions.middleware.SessionMiddleware',
'django.middleware.common.CommonMiddleware',
'django.middleware.csrf.CsrfViewMiddleware',
'django.contrib.auth.middleware.AuthenticationMiddleware',
'django.contrib.messages.middleware.MessageMiddleware',
'django.middleware.clickjacking.XFrameOptionsMiddleware',
]

# ... 100+ more lines for static files, templates, logging, etc.

Issues: ❌ 200+ lines of configuration code ❌ Manual type conversion everywhere ❌ String parsing errors go unnoticed ❌ No validation until runtime ❌ No IDE autocomplete ❌ Hard to test different configurations ❌ Environment variables scattered everywhere

Django-CFG Approach (30 lines)

# config.py
from django_cfg import DjangoConfig, DatabaseConfig, CacheConfig, EmailConfig
from typing import Dict
from .environment import env # Type-safe YAML config loader

class MyConfig(DjangoConfig):
"""Complete production configuration with type safety"""

# Security - validated at startup
project_name: str = "My Project"
secret_key: str = env.secret_key # Type-safe from YAML
debug: bool = False

# Single field auto-generates: ALLOWED_HOSTS, CORS_*, CSRF_TRUSTED_ORIGINS, SSL settings
security_domains: list[str] = ["myapp.com"]

# Database - type-safe, validated
databases: Dict[str, DatabaseConfig] = {
"default": DatabaseConfig(
engine="django.db.backends.postgresql",
name=env.database.name,
user=env.database.user,
password=env.database.password,
host=env.database.host,
port=env.database.port, # Already int from Pydantic
)
}

# Cache - auto-created from redis_url! ✨
redis_url: str = f"redis://{env.redis.host}:{env.redis.port}/0"
# No cache_default needed - Django-CFG creates it automatically!

# Email - type-safe with validation
email: EmailConfig = EmailConfig(
backend="smtp",
host=env.email.host,
port=env.email.port,
use_tls=True,
username=env.email.username,
password=env.email.password,
)

# Built-in apps - enable with boolean flags
enable_support: bool = True # Support ticket system
enable_accounts: bool = True # Extended user management

# settings.py - just 2 lines
config = MyConfig()
globals().update(config.get_all_settings())

Benefits:85% less code (30 lines vs 200+) ✅ Type-safe - Pydantic validates at startup ✅ IDE autocomplete - all fields discovered ✅ Single security field - auto-generates 5+ Django settings ✅ Validated env - YAML config with Pydantic models ✅ No runtime errors - fails before Django loads ✅ Easy testing - just instantiate config class

Lines of Code Reduction

Traditional Django Project:
├── settings.py: 200+ lines
├── settings_dev.py: 150+ lines
├── settings_prod.py: 150+ lines
├── env parsing: 50+ lines
└── Total: 550+ lines

Django-CFG Project:
├── config.py: 30 lines
├── environment.py: 20 lines
└── Total: 50 lines

Reduction: 90% fewer lines of configuration code

Real-World Use Cases

1. E-Commerce Platform (Multi-Database Setup)

Challenge: Product catalog in PostgreSQL, session cache in Redis, order analytics in separate read replica.

Traditional Django: 80+ lines of database routing, cache configuration, manual connection management.

With Django-CFG:

class ECommerceConfig(DjangoConfig):
security_domains: list[str] = ["shop.example.com"]

databases: Dict[str, DatabaseConfig] = {
"default": DatabaseConfig(
engine="django.db.backends.postgresql",
name=env.db.products.name,
host=env.db.products.host,
),
"analytics": DatabaseConfig( # Read replica
engine="django.db.backends.postgresql",
name=env.db.analytics.name,
host=env.db.analytics.host,
),
}

# Auto Redis cache - just set redis_url! ✨
redis_url: str = f"redis://{env.redis.host}:6379/0"

enable_support: bool = True # Customer support tickets
enable_newsletter: bool = True # Marketing campaigns

Result: 15 lines vs 80+ lines. Type-safe. Zero database routing bugs.

2. SaaS Application (Environment-Specific Configuration)

Challenge: Different settings for dev/staging/prod. SSL in production only. Debug emails in development.

Traditional Django: Multiple settings files (settings_dev.py, settings_prod.py), inheritance issues, settings conflicts.

With Django-CFG:

class SaaSConfig(DjangoConfig):
project_name: str = "MySaaS Platform"
secret_key: str = env.secret_key

# Automatically detects environment from ENV variable
debug: bool = False # Auto-overridden in development

# SSL auto-enabled in production, disabled in dev
security_domains: list[str] = ["app.mysaas.com"]

# Email backend auto-switches based on environment
email: EmailConfig = EmailConfig(
backend="console" if env.environment == "development" else "smtp",
host=env.email.host if env.environment != "development" else "localhost",
)

# JWT for API authentication
jwt: JWTConfig = JWTConfig(
signing_key=env.jwt_key,
access_token_lifetime=3600, # 1 hour
refresh_token_lifetime=86400, # 24 hours
)

enable_accounts: bool = True # User management with OTP
enable_agents: bool = True # AI-powered customer support

Result: One config file for all environments. Automatic environment detection. No settings file sprawl.

3. Enterprise API Service (Complex Integrations)

Challenge: Integrate with Twilio (SMS), SendGrid (email), Stripe (payments), OpenAI (AI), Redis (cache), PostgreSQL (data).

Traditional Django: 10+ third-party packages, 150+ lines of configuration, manual credential management, integration complexity.

With Django-CFG:

from django_cfg import (
DjangoConfig, DatabaseConfig, TwilioConfig, TwilioVerifyConfig,
TwilioChannelType, EmailConfig, TaskConfig, SpectacularConfig
)
from pydantic import SecretStr
from typing import Dict

class APIServiceConfig(DjangoConfig):
security_domains: list[str] = ["api.enterprise.com"]

# Database
databases: Dict[str, DatabaseConfig] = {
"default": DatabaseConfig(
engine="django.db.backends.postgresql",
name=env.database.name,
host=env.database.host,
conn_max_age=600, # Connection pooling
)
}

# Twilio integration with OTP verification
twilio: TwilioConfig = TwilioConfig(
account_sid=env.twilio.account_sid,
auth_token=SecretStr(env.twilio.auth_token),
verify=TwilioVerifyConfig(
service_sid=env.twilio.verify_service_sid,
default_channel=TwilioChannelType.WHATSAPP,
fallback_channels=[TwilioChannelType.SMS],
),
)

# Email configuration
email: EmailConfig = EmailConfig(
host=env.email.host,
port=env.email.port,
use_tls=True,
)

# AI-powered features built-in
enable_agents: bool = True # AI agent orchestration
enable_knowbase: bool = True # AI knowledge base

# Background tasks for async processing
task: TaskConfig = TaskConfig(
broker_url=f"redis://{env.redis.host}:6379/0",
result_backend=f"redis://{env.redis.host}:6379/1",
)

# API documentation auto-generated
spectacular: SpectacularConfig = SpectacularConfig(
title="Enterprise API",
description="Production API with authentication",
version="2.0.0",
)

Result: All integrations type-safe and validated. 40 lines vs 150+. Built-in AI agents. Auto-generated API docs.

4. Startup MVP (Rapid Development)

Challenge: Build and deploy in 1 day. Support tickets, user auth, payments, email notifications.

Traditional Django: Install 8-10 packages, configure each one, wire everything together, write custom user model, build support system.

With Django-CFG:

class MVPConfig(DjangoConfig):
project_name: str = "Startup MVP"
secret_key: str = env.secret_key
security_domains: list[str] = ["mvp.startup.io"]

# Production-ready database
databases: Dict[str, DatabaseConfig] = {
"default": DatabaseConfig(
engine="django.db.backends.postgresql",
name=env.database.name,
host=env.database.host,
)
}

# Enable built-in production features
enable_support: bool = True # Support ticket system (Django app)
enable_accounts: bool = True # Extended user management with OTP
enable_newsletter: bool = True # Email campaigns
enable_leads: bool = True # Lead collection forms

# Email notifications
email: EmailConfig = EmailConfig(
backend="smtp",
host="smtp.gmail.com",
port=587,
use_tls=True,
username=env.email.username,
password=env.email.app_password,
)

One command deployment:

django-cfg create-project startup_mvp
cd startup_mvp
python manage.py migrate
python manage.py runserver

Result: Full production features in 20 lines of config. Deploy in hours, not days.

Success Metrics from Real Projects

MetricBefore Django-CFGAfter Django-CFGImprovement
Setup Time2-3 days15 minutes99% faster
Config Bugs8-10 per year0-1 per year90% reduction
Onboarding Time1 week2 hours97% faster
Config Lines550+ lines50 lines90% less code
Third-Party Deps8-10 packages1 packageBuilt-in
Type ErrorsCaught at runtimeCaught at startupZero downtime

Key Concepts

Type Safety with Pydantic v2

Django-CFG uses Pydantic BaseModel for all configuration. This means:

  • Validation at startup: Invalid configuration fails before Django loads
  • Type checking: mypy/pyright can verify your configuration
  • IDE support: Full autocomplete for all configuration fields
  • Documentation: Field descriptions become IDE hints

Configuration Lifecycle

1. Define config class (inherits DjangoConfig)

2. Instantiate config (validates fields, loads env vars)

3. Call get_all_settings() (generates Django settings dict)

4. Django loads with validated configuration

Environment Configuration Pattern

Django-CFG uses a type-safe environment loader with Pydantic validation:

# environment.py
from pydantic_yaml import parse_yaml_raw_as
from pydantic import BaseModel
from pathlib import Path

class DatabaseEnv(BaseModel):
name: str
user: str
password: str
host: str
port: int = 5432

class EnvironmentConfig(BaseModel):
secret_key: str
database: DatabaseEnv

# Load and validate environment from YAML
config_path = Path(__file__).parent / "config.yaml"
env: EnvironmentConfig = parse_yaml_raw_as(
EnvironmentConfig,
config_path.read_text()
)

Basic Example

from django_cfg import DjangoConfig, DatabaseConfig, CacheConfig
from typing import Dict
from .environment import env

class MyConfig(DjangoConfig):
"""Project configuration with type validation"""

# Type-safe environment loading
secret_key: str = env.secret_key

# Boolean field with default
debug: bool = False

# Security domains - replaces ALLOWED_HOSTS
# Will auto-generate ALLOWED_HOSTS, CORS settings, SSL config
security_domains: list[str] = ["myapp.com"]

# Database with type-safe environment values
databases: Dict[str, DatabaseConfig] = {
"default": DatabaseConfig(
engine="django.db.backends.postgresql",
name=env.database.name,
user=env.database.user,
password=env.database.password,
host=env.database.host,
port=env.database.port,
)
}

# Optional cache configuration
cache_default: CacheConfig | None = None

# In settings.py
config = MyConfig()
globals().update(config.get_all_settings())

Why Use Django-CFG?

Problem: Traditional Django Settings

Traditional Django settings.py has several issues for production systems:

  1. No type validation: DEBUG = os.environ.get('DEBUG', 'False') == 'True' - easy to get wrong
  2. No field validation: Missing required variables only fail at runtime
  3. No IDE support: No autocomplete, no type hints
  4. Hard to test: Settings are global, difficult to test different configurations
  5. Environment management: Manually check environment and set values

Solution: Type-Safe Configuration Models

Django-CFG solves these with Pydantic models:

# Traditional settings.py - prone to errors
DEBUG = os.environ.get('DEBUG', 'False') == 'True' # String comparison!
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.postgresql',
'PORT': os.environ.get('DB_PORT', '5432'), # Still a string!
}
}

# Django-CFG - type-safe and validated
class MyConfig(DjangoConfig):
debug: bool = False # Pydantic validates boolean conversion
databases: Dict[str, DatabaseConfig] = {
"default": DatabaseConfig(
engine="django.db.backends.postgresql",
port=5432, # Integer type enforced
)
}

Comparison with Alternatives

FeatureDjango-CFGdjango-environpython-decouple
Type SafetyPydantic v2 modelsRuntime castingRuntime casting
ValidationField validatorsManualManual
IDE SupportFull autocompleteNoneNone
Error MessagesDetailed with contextGenericGeneric
Nested ConfigYes (models)NoNo
TestingEasy (instantiate config)Difficult (global)Difficult (global)

When to Use Django-CFG

  • Production applications where configuration errors are critical
  • Teams using type hints and mypy/pyright
  • Projects with complex configuration (multiple databases, caching strategies)
  • Applications requiring validated configuration at startup
  • Projects needing multi-environment support (dev/staging/prod)

Consider Alternatives If

  • Simple Django project with <10 configuration values
  • Team doesn't use type hints
  • Legacy codebase without resources for refactoring
  • No automated testing or CI/CD

⚠️ Migration Note: Moving from settings.py to Django-CFG requires refactoring. Plan for testing time.

Quick Start

1. Install

pip install django-cfg

2. Create Config Class

# config.py
from django_cfg import DjangoConfig, DatabaseConfig
from typing import Dict

class MyConfig(DjangoConfig):
project_name: str = "My Project"
secret_key: str = "<from-yaml-config>" # Set via environment/config.yaml # From environment
debug: bool = False

# security_domains replaces ALLOWED_HOSTS
security_domains: list[str] = ["localhost"]

databases: Dict[str, DatabaseConfig] = {
"default": DatabaseConfig(
engine="django.db.backends.postgresql",
name="<from-yaml-config>", # Set via environment/config.yaml
user="<from-yaml-config>", # Set via environment/config.yaml
password="<from-yaml-config>", # Set via environment/config.yaml
host="<from-yaml-config>", # Set via environment/config.yaml
port=5432,
)
}

3. Use in Settings

# settings.py
from config import MyConfig

config = MyConfig()
globals().update(config.get_all_settings())

4. Set Environment Variables

export SECRET_KEY="your-secret-key-here-min-50-chars-long-or-validation-fails"
export DB_NAME="myapp"
export DB_USER="postgres"
export DB_PASSWORD="password"

5. Verify Configuration

python manage.py check

If configuration is invalid, Django-CFG will show detailed error messages before Django loads.

Built-in Features

Django-CFG includes optional integrations that can be enabled via configuration flags:

Core Configuration

  • Environment detection: Automatically detects development/production/test based on environment variables
  • Security domains: Single field that auto-configures ALLOWED_HOSTS, CORS, SSL redirects
  • Database routing: Declarative database routing without custom router classes
  • Startup validation: All configuration validated before Django starts

Optional Built-in Apps

Enable via boolean flags in your config:

class MyConfig(DjangoConfig):
enable_support: bool = True # Support ticket system
enable_accounts: bool = True # Extended user management with OTP
enable_newsletter: bool = False # Email campaigns
enable_leads: bool = False # Lead collection
enable_agents: bool = False # AI agents framework
enable_knowbase: bool = False # AI-powered knowledge base
enable_maintenance: bool = False # Multi-site maintenance mode

Integrations

  • Unfold: Modern admin interface (replaces default Django admin)
  • Django-CFG API Client Generation: API zones with automatic OpenAPI documentation
  • ReArq: Background task processing (auto-enabled when needed)
  • Twilio: SMS/voice integration
  • Telegram: Bot integration for notifications
  • Email: SendGrid, SMTP, or console backend

See Features documentation for details on each integration.

Architecture Overview

DjangoConfig (Pydantic Model)

Field Validation (Pydantic)

Environment Variable Substitution

Smart Defaults Application

Django Settings Generation

settings.py globals

Key concepts:

  1. Single source of truth: Config class defines all settings
  2. Fail-fast validation: Errors at instantiation, not during requests
  3. Immutable config: Configuration can't be changed after instantiation
  4. Type-safe everywhere: Full IDE support from config to Django settings

Next Steps

New to Django-CFG?

  1. Installation Guide - Install and verify setup
  2. First Project - Create a project from scratch
  3. Configuration Reference - All configuration options

Migrating Existing Project?

  1. Migration Guide - Step-by-step migration from settings.py
  2. Production Configuration - Production best practices
  3. Multi-Database Setup - Complex database routing

Need Specific Features?

Resources

Documentation:

Community:


Ready to start?Installation Guide