pydantic-settings
pydantic-settings extends Pydantic models to load configuration from environment variables, .env files, and optional secrets directories - with validation, type coercion, and clear error messages at startup.
Search across all documentation pages
pydantic-settings extends Pydantic models to load configuration from environment variables, .env files, and optional secrets directories - with validation, type coercion, and clear error messages at startup.
from pydantic import Field
from pydantic_settings import BaseSettings, SettingsConfigDict
class Settings(BaseSettings):
model_config = SettingsConfigDict(env_file=".env", extra="ignore")
database_url: str = Field(alias="DATABASE_URL")
debug: bool = False
api_key: str
settings = Settings() # raises ValidationError if api_key missingWhen to reach for this:
os.environos.getenv("PORT", "8000") castsfrom __future__ import annotations
from functools import lru_cache
from pydantic import Field, PostgresDsn
from pydantic_settings import BaseSettings, SettingsConfigDict
class Settings(BaseSettings):
model_config = SettingsConfigDict(
env_file=".env",
env_file_encoding="utf-8",
extra="ignore",
)
app_name: str = "billing-api"
database_url: PostgresDsn = Field(alias="DATABASE_URL")
redis_url: str = Field(default="redis://localhost:6379/0", alias="REDIS_URL")
log_level: str = "INFO"
@lru_cache
def get_settings() -> Settings:
return Settings()
def main() -> None:
s = get_settings()
print(s.app_name, s.database_url, s.log_level)
if __name__ == "__main__":
main()What this demonstrates:
SettingsConfigDict centralizes env file and ignore-unknown-keys policyField(alias=...) maps screaming-snake env names to snake_case attrsPostgresDsn validates connection string shape early@lru_cache gives FastAPI-style singleton settings without globals"true" becomes True for bool fields; comma-separated strings can become lists with custom parsers.env_nested_delimiter="__" maps DB__HOST to db.host.secrets_dir loads Docker/Kubernetes mounted secret files as field values.| Field Type | Env Example | Notes |
|---|---|---|
str | API_KEY=abc | Required if no default |
bool | DEBUG=true | Accepts true/false/1/0 |
int | PORT=8000 | Invalid values raise at startup |
list[str] | ORIGINS=a,b | Use NoDecode + custom validator for CSV |
SecretStr | TOKEN=... | Masks value in repr/logs |
from pydantic import SecretStr
class Settings(BaseSettings):
token: SecretStr
# SecretStr hides in logs
print(settings.token.get_secret_value()) # explicit revealsettings = Settings() at module level fails in tests. Fix: factory + lru_cache or dependency injection.extra="allow" in production - Typos in env names silently create attributes. Fix: extra="ignore" or "forbid"..env with secrets - Git history leaks credentials. Fix: .env.example only; load real values from platform secrets.ApiSettings, WorkerSettings per deployable.Optional[int] = None and validate..env.example.| Alternative | Use When | Don't Use When |
|---|---|---|
os.environ + manual casts | Tiny scripts with 2-3 vars | More than a handful of settings |
python-decouple | Legacy Django projects already using it | Greenfield Pydantic/FastAPI stacks |
dynaconf | Multi-environment YAML/TOML layering | You want Pydantic types end-to-end |
django-environ | Django-only configuration | FastAPI or standalone workers |
Clear the cache and pass env via monkeypatch.setenv before calling get_settings() - or use Settings(_env_file=None, api_key="test") constructor overrides.
Pass env_file=(".env", ".env.local") - later files do not override earlier unless real env vars win.
Define a nested BaseModel field and set env_nested_delimiter="__" so CACHE__TTL=300 maps correctly.
Yes - def settings_dep() -> Settings: return get_settings() and inject into routes.
Use @model_validator(mode="after") to raise if both USE_SQLITE and DATABASE_URL are set.
port: int = Field(default=8000, alias="PORT") - Heroku/Fly/Railway inject PORT automatically.
pydantic-settings focuses on env; for TOML-heavy apps combine with pyproject.toml section loaders or dynaconf.
Generate JSON schema from the model or maintain .env.example with every key and sample value.
Use SecretStr for tokens and passwords so accidental logging does not print secrets.
Yes in standalone scripts and ASGI workers; Django projects often use django-environ but Pydantic settings work for Celery workers sharing the same env.
Stack versions: This page was written for Python 3.14.0 (stable 3.14, maintenance 3.13), FastAPI 0.115+, Django 5.2, Flask 3.1, Pydantic 2, PyTorch 2.6+, pandas 2.2+, Polars 1.x, ruff 0.9+, and uv 0.6+.
Reviewed by Chris St. John·Last updated Jul 16, 2026