Skip to content

Recipes

These are task-oriented flows. Each recipe links to the deeper reference page when you need more detail.

Load Defaults Plus an Environment File

Use the named-files model for new projects:

.confii.yaml
default_environment: development
env_switcher: APP_ENV
environment_strategy: named_files
sources:
  - type: environment_files
    search_paths: [config]
    default_file: default.yaml
    environment_file: "{environment}.yaml"
APP_ENV=production
  config/default.yaml
  + config/production.yaml

The selected environment file is a higher-precedence layer and is merged with the configured merge strategy.

See Environment Resolution.

Override a File Value With an Environment Variable

.confii.yaml
env_prefix: MYAPP
MYAPP_SERVER__PORT=9090 go run .

MYAPP_SERVER__PORT maps to server.port and is applied after declared sources.

See Sources.

Preview the Exact Load Plan

confii plan
APP_ENV=production confii plan
APP_ENV=production confii plan --json

Use this before deployments to confirm environment selection, source order, and mixed-model conflicts.

See CLI.

Explain a Surprising Value

confii explain production --key database.host

In Go:

info := cfg.Explain("database.host")
fmt.Printf("%+v\n", info)

See Introspection.

Add a Custom Sensitive Path

.confii.yaml
sensitive_paths:
  - database.password
  - token
  - cloud.api_key

Confii redacts these paths in debug reports, diffs, and documentation surfaces that expose values.

See Secrets and Introspection.

Validate Before Publication

.confii.yaml
validate_on_load: true
strict_validation: true
type AppConfig struct {
    Server struct {
        Port int `confii:"port" validate:"required,min=1,max=65535"`
    } `confii:"server"`
}

cfg, err := confii.New[AppConfig]()

Invalid startup, reload, extension, override, or mutation candidates are rejected before publication.

See Validation.

Add a Runtime Tenant Overlay

err := cfg.ExtendWithContext(ctx, loader.NewYAML("tenants/acme.yaml"))
if err != nil {
    return err
}

Extend registers the new loader for future reloads and publishes only after the candidate passes materialization and validation.

See Lifecycle.

Test With Temporary Overrides

restore, err := cfg.Override(map[string]any{
    "server.port": 18080,
})
require.NoError(t, err)
defer restore()

Use Override for scoped tests. Use a fresh config with WithWorkingDir for fixture-based tests.

See Testing.

Resolve Secrets From a Custom Store

resolver := secret.NewResolver(store, secret.WithCache(true))
cfg, err := confii.New[AppConfig](
    confii.WithSecretResolver(resolver),
)

Config value:

database:
  password: ${secret:database/password}

See Extensibility and Secrets.

Share Structured Values Across Files

Enable structured references when one file owns reusable structured values and another source should read a specific field:

cfg, err := confii.New[AppConfig](
    confii.WithWorkingDir("/srv/app"),
    confii.WithStructuredResolver(true),
)
shared.yaml
server:
  port: 8080
  token: ${secret:service/token}
config.yaml
server:
  port: ${yaml:shared.yaml#server.port}
  token: ${yaml:shared.yaml#server.token}

The reference is resolved before secret resolution, so the token is still resolved through the configured secret provider.

See Sources.

Add a Custom Value Resolver

cfg, err := confii.New[AppConfig](
    confii.WithValueResolver("tenant", func(ctx context.Context, req hook.ResolverRequest) (any, error) {
        if err := ctx.Err(); err != nil {
            return nil, err
        }
        return lookupTenantValue(ctx, req.Target, req.Fragment)
    }),
)
tenant_name: ${tenant:acme#display_name}

See Extensibility.

Detect Drift in CI

baseline := loadExpectedConfig()
drifts, err := cfg.DetectDrift(baseline)
if err != nil {
    return err
}
if len(drifts) > 0 {
    return fmt.Errorf("configuration drift detected")
}

See Diff & Drift.

Generate Configuration Documentation

confii docs production -f markdown -o CONFIG.md

In Go:

doc, err := cfg.GenerateDocs("markdown")

See Export.