Services

Configuration

Read layered configuration with compass, from plugin defaults through per-environment files and SUMMER_ variables to runtime overrides saved to disk.

On this page

WinterCMS reads configuration with Config::get('app.name') from config/*.php, per-environment directories and .env. SummerCMS reads it from a compass.Config built by compass, with the same dot paths. Setup, Configuration lists the keys an application sets; this page covers how the layers merge and how code reads and changes them.

Layers#

compass.Config merges its sources in a fixed order. Each layer overrides the ones before it:

  1. Plugin defaults, merged with compass.Config.MergePlugin when the plugin is activated. A plugin's config/config.yaml becomes <plugin id>.<key>, so acme.blog.posts_per_page is the WinterCMS acme.blog::posts_per_page. Any other config/<name>.yaml becomes <plugin id>.<name>.<key>.
  2. config/*.yaml. Each file is a section named after the file, so config/app.yaml provides app.*.
  3. config/env/<environment>/*.yaml, the per-environment sections.
  4. SUMMER_ environment variables and the .env file next to config/. SUMMER_MAIL__DRIVER sets mail.driver: the prefix is removed, __ separates the path segments and the name is lower-cased. A .env value applies only when the real environment does not set the same variable.
  5. config/env/<environment>/overrides.yaml, written by compass.Config.Persist.
  6. Values set in memory with compass.Config.Set.

The environment comes from SUMMER_ENV and defaults to production. Its name may contain only letters, digits, - and _.

Reading values#

The typed getters compass.Config.String, compass.Config.Int and compass.Config.Bool return the zero value for a missing key. Use compass.Config.Lookup or compass.Config.Has when a missing key must be told apart from a zero value, and compass.Config.LoadSection to read a whole section into a struct with koanf tags:

modules/compass/example_test.go#ExampleOpen
dir, err := os.MkdirTemp("", "acme-config")
if err != nil {
	fmt.Println(err)
	return
}
defer os.RemoveAll(dir)
if err := writeConfig(dir); err != nil {
	fmt.Println(err)
	return
}

// Environ stands in for the process environment (nil reads os.Environ).
cfg, err := compass.Open(compass.Options{
	Dir:     dir,
	Env:     "development",
	Environ: []string{"SUMMER_MAIL__DRIVER=smtp"},
})
if err != nil {
	fmt.Println(err)
	return
}

// A plugin's embedded config/config.yaml becomes its defaults.
plugin := fstest.MapFS{"config/config.yaml": {Data: []byte("posts_per_page: 10\n")}}
if err := cfg.MergePlugin("acme.blog", plugin); err != nil {
	fmt.Println(err)
	return
}

fmt.Println(cfg.String("app.name"), cfg.Bool("app.debug"), cfg.Int("acme.blog.posts_per_page"))
var mail mailSettings
if err := cfg.LoadSection("mail", &mail); err != nil {
	fmt.Println(err)
	return
}
fmt.Println(mail.Driver, mail.From)
_, found := cfg.Lookup("app.timezone")
fmt.Println(cfg.Environment(), found)

// A runtime override, saved to env/development/overrides.yaml.
if err := cfg.Set("acme.blog.posts_per_page", 25); err != nil {
	fmt.Println(err)
	return
}
if err := cfg.Persist(); err != nil {
	fmt.Println(err)
	return
}
saved, _ := os.ReadFile(filepath.Join(dir, "env", "development", "overrides.yaml"))
fmt.Print(string(saved))
// Output:
// Acme true 10
// smtp blog@example.com
// development false
// acme:
//     blog:
//         posts_per_page: 25

The application opens its configuration once with compass.Load("config") in the generated main and hands it to the backpack.App. Plugins read it through app.Config and never open their own.

Changing values at runtime#

compass.Config.Set changes a value in memory. compass.Config.Persist saves every value set at runtime to config/env/<environment>/overrides.yaml, keeping the keys already saved there. It replaces the file atomically, creates it readable only by its owner and refuses any path outside the config directory. compass.Config.Reload rereads every source and discards values that were set but not persisted.

Values that admins edit in the backend are not configuration: settings pages store them in a database row; see Settings.