Skip to content

YAML Configuration

QuadKit merges user-defined YAML files, environment variables, and code defaults into a single typed configuration object. This page covers the mechanics; for a task-oriented walkthrough see Configuration.

The primary file is application.yaml in the project root. Core settings are top-level; each extension reads its own named section:

application.yaml
app_name: "order-service"
debug: false
env: "production"
logging:
level: INFO
json_format: true
web: # quadkit-web (config_section: "web")
server:
host: "${HOST:0.0.0.0}"
port: "${PORT:8000}"
cli: # quadkit-cli (config_section: "cli")
enabled: true
color: true
from quadkit import QuadKitConfig
config = QuadKitConfig.from_yaml() # ./application.yaml (CWD)
config = QuadKitConfig.from_yaml("config/application.yaml")
config = QuadKitConfig.from_env_profile() # recommended: same file, plus QK_PROFILE

Relative paths resolve from the process CWD. A missing file is not an error — QuadKit logs config.defaults_only and uses code defaults. Both loaders overlay application.{profile}.yaml when QK_PROFILE is set (or profile= is passed), then apply QK_* env vars. Application() with no config calls from_env_profile().


QuadKit resolves ${VAR} placeholders inside YAML values at load time:

  • ${PORT} — resolves to the PORT env var; fails fast if unset.
  • ${PORT:8080} — resolves to PORT, or 8080 if unset.
web:
server:
host: "${HOST:0.0.0.0}"
port: "${PORT:8000}"

Beyond interpolation, any key can be overridden by an environment variable using the QK_ prefix and double underscores (__) for nesting. This is the highest-priority source:

sql.backend.url → QK_SQL__BACKEND__URL
web.server.port → QK_WEB__SERVER__PORT
auth.secret_key → QK_AUTH__SECRET_KEY

The prefix is stripped and the rest is lowercased; __ becomes nesting. List indexes are not special — QK_FOO__0__BAR becomes a dict key "0", not foo[0].

Terminal window
QK_WEB__SERVER__PORT=9000 quadkit run

Override base settings per environment with profile files. Activate a profile with QK_PROFILE:

Terminal window
QK_PROFILE=production quadkit run
  • Base: application.yaml
  • Overlay: application.{profile}.yaml (e.g. application.production.yaml)

When resolving a key, QuadKit applies sources in this order (highest priority wins):

  1. QK_ environment variables — QK_WEB__SERVER__PORT=9000 overrides everything
  2. Profile YAML — values from application.{profile}.yaml
  3. Base YAML — values from application.yaml
  4. Code defaults — defined in each config model

QuadKitConfig exposes typed top-level fields and resolves extension sections on demand:

config = QuadKitConfig.from_yaml()
# Typed top-level
config.app_name # "order-service"
config.debug # False
config.environment # Environment.PRODUCTION
# Extension sections — pass the config model to get a typed object back
web_config = config.get_section("web", WebConfig)
# Dotted paths
cors = config.get("web.security.cors.allowed_origins")
# Existence check
config.has_section("web") # True

Providers rarely call get_section() themselves — declaring config_key and config_model makes the framework inject the typed section automatically. See Configuration → auto-injection.


application.development.yaml
debug: true
logging:
level: DEBUG
json_format: false
web:
server:
port: 9000
application.production.yaml
debug: false
logging:
level: WARNING
json_format: true
web:
server:
port: "${PORT:8000}"
security:
cors:
enabled: true
allowed_origins: ["https://myapp.com"]

Inspect the resolved tree (secrets masked):

Terminal window
quadkit config show
quadkit config validate
quadkit config doctor --env production