Minimal examples to load single and multiple config files with clear precedence
This tutorial shows how to load configuration from one or more files using Glazed middlewares. You’ll see a simple single-file setup and a multi-file overlay with deterministic precedence. We’ll also show how to inspect parse steps using --print-parsed-fields.
For CLIs, the normal entry point is CobraParserConfig: use AppName for env overrides and ConfigPlanBuilder for file discovery. For library-style middleware execution, the equivalent direct APIs are sources.FromConfigPlan(...) and sources.FromConfigPlanBuilder(...).
Create a minimal command with a single custom section and an explicit config file path:
demoSection, _ := schema.NewSection(
"demo", "Demo settings",
sections.WithPrefix("demo-"),
schema.WithFields(
fields.New("api-key", fields.TypeString),
fields.New("threshold", fields.TypeInteger, fields.WithDefault(10)),
),
)
desc := cmds.NewCommandDescription("demo", cmds.WithSectionsList(demoSection))
cmd := &DemoBareCommand{CommandDescription: desc}
cobraCmd, _ := cli.BuildCobraCommandFromCommand(cmd,
cli.WithParserConfig(cli.CobraParserConfig{
AppName: "demo",
SkipCommandSettingsSection: true,
ConfigPlanBuilder: func(_ *values.Values, _ *cobra.Command, _ []string) (*config.Plan, error) {
return config.NewPlan(
config.WithLayerOrder(config.LayerExplicit),
).Add(
config.ExplicitFile("./config.yaml").Named("single-config"),
), nil
},
}),
)
Example config.yaml:
demo:
api-key: cfg-one
threshold: 33
Run:
go run ./cmd/examples/config-single demo
Expected output:
api_key=cfg-one threshold=33
Use a config plan to return an ordered set of files (low → high precedence):
cobraCmd, _ := cli.BuildCobraCommandFromCommand(cmd,
cli.WithParserConfig(cli.CobraParserConfig{
AppName: "demo",
SkipCommandSettingsSection: true,
ConfigPlanBuilder: func(_ *values.Values, _ *cobra.Command, _ []string) (*config.Plan, error) {
return config.NewPlan(
config.WithLayerOrder(config.LayerSystem, config.LayerUser, config.LayerCWD),
).Add(
config.ExplicitFile("base.yaml").Named("base").InLayer(config.LayerSystem),
config.ExplicitFile("env.yaml").Named("env").InLayer(config.LayerUser),
config.ExplicitFile("local.yaml").Named("local").InLayer(config.LayerCWD),
), nil
},
}),
)
Example files (low → high):
# base.yaml
demo:
api-key: base
threshold: 5
# env.yaml
demo:
api-key: env-file
threshold: 12
# local.yaml
demo:
api-key: local
threshold: 20
Run:
go run ./cmd/examples/config-overlay overlay
Expected output:
api_key=local threshold=20
Under the hood, the Cobra parser now uses the same plan-loading middleware shape you can call directly in library code:
sources.Execute(schema_, parsed,
sources.FromDefaults(),
sources.FromConfigPlan(plan),
sources.FromEnv("DEMO"),
sources.FromArgs(args),
sources.FromCobra(cmd),
)
Add --print-parsed-fields to see each config file applied in sequence:
go run ./cmd/examples/config-overlay overlay --print-parsed-fields
Excerpt:
demo:
threshold:
log:
- source: config
metadata: { config_file: base.yaml, index: 0 }
value: 5
- source: config
metadata: { config_file: env.yaml, index: 1 }
value: 12
- source: config
metadata: { config_file: local.yaml, index: 2 }
value: 20
value: 20
Precedence remains: Defaults < Config Files < Env < Args < Flags.
# Env override
DEMO_API_KEY=env-var go run ./cmd/examples/config-overlay overlay --print-parsed-fields
# Flag override
go run ./cmd/examples/config-overlay overlay --demo-threshold 77 --print-parsed-fields
Example parse excerpt for the env override:
demo:
api-key:
log:
- source: config
metadata: { config_file: base.yaml, index: 0 }
value: base
- source: env
value: env-var
value: env-var
To section <base>.override.yaml automatically on top of --config-file:
resolver := func(parsed *values.Values, _ *cobra.Command, _ []string) ([]string, error) {
cs := &cli.CommandSettings{}
_ = parsed.DecodeSectionInto(cli.CommandSettingsSlug, cs)
files := []string{}
if cs.ConfigFile != "" {
files = append(files, cs.ConfigFile)
dir := filepath.Dir(cs.ConfigFile)
base := filepath.Base(cs.ConfigFile)
ext := filepath.Ext(base)
stem := strings.TrimSuffix(base, ext)
override := filepath.Join(dir, fmt.Sprintf("%s.override.yaml", stem))
if _, err := os.Stat(override); err == nil {
files = append(files, override)
}
}
return files, nil
}
Run:
go run ./cmd/examples/overlay-override overlay-override --config-file ./base.yaml
Map arbitrary config structures to fields without custom Go by using the pattern-based config mapper. Works with YAML or JSON files.
// Define a section
demoSection, _ := schema.NewSection("demo", "Demo",
schema.WithFields(
fields.New("api-key", fields.TypeString),
fields.New("dev-api-key", fields.TypeString),
fields.New("prod-api-key", fields.TypeString),
),
)
paramSections := schema.NewSchema(sections.WithSections(demoSection))
// Create a mapper using a named capture {env}
mapper, _ := patternmapper.NewConfigMapper(paramSections,
patternmapper.MappingRule{
Source: "app.{env}.settings",
TargetSection: "demo",
Rules: []patternmapper.MappingRule{
{Source: "api_key", TargetField: "{env}-api-key"},
},
},
)
// Use the mapper when loading the file
mw := sources.FromFile("config.yaml",
middlewares.WithConfigMapper(mapper),
)
_ = sources.Execute(paramSections, values.New(), mw)
Builder API (fluent):
b := patternmapper.NewConfigMapperBuilder(paramSections).
MapObject("app.{env}.settings", "demo", []patternmapper.MappingRule{
patternmapper.Child("api_key", "{env}-api-key"),
})
mapper, _ := b.Build()
You can execute middlewares directly without relying on the Cobra parser config:
err := sources.Execute(schema_, parsed,
sources.FromDefaults(),
sources.FromFiles([]string{"base.yaml", "local.yaml"}),
sources.FromEnv("APP"),
sources.FromCobra(cmd), // flags & args
)
Required patterns: mark mappings as required so missing keys error out.
patternmapper.MappingRule{
Source: "app.settings.api_key",
TargetSection: "demo",
TargetField: "api-key",
Required: true,
}
Ambiguity: wildcard patterns that match multiple different values or rules that resolve to the same target field cause errors. Prefer named captures (e.g., app.{env}.api_key) when collecting multiple values.
Missing fields: mapping to a non-existent field errors (prefix-aware), helping catch typos early.
Older Viper-based config parsing helpers were removed. Prefer config file middlewares plus env and flags:
err := sources.Execute(schema_, parsed,
sources.FromDefaults(),
sources.FromFiles([]string{"base.yaml", "env.yaml", "local.yaml"}),
sources.FromEnv("APP"),
sources.FromCobra(cmd),
)
See the validation guides for full explanations and code snippets:
glaze help config-files
glaze help pattern-based-config-mapping
glaze help config-files
glaze help pattern-based-config-mapping
glaze help cmds-middlewares
cmd/examples/config-overlay, cmd/examples/config-pattern-mapper