Files
summercms/docs/services/configuration.md
Jakub Zych efb35a2d35 feat(11.1-04): add the Database section and the core Services pages
- docs/database: models, migrations, queries and pagination, relations,
  casts and validation, attachments and transactions (lagoon.Transaction,
  lagoon.AfterCommit, nested savepoints, lagoon.OnDatabase)
- docs/services: configuration, events, routing with auth groups, rate
  limiting, authentication, the OAuth server, mail and localization
- runnable Examples for lagoon, attach, compass, surf, wire, bouncer,
  wristband, postcard, phrasebook and festival; lagoon TestDocs* regions
  run on the package's Postgres harness through DocsDB
- 15 new required pages
2026-09-30 22:59:25 +02:00

4.5 KiB

title, description, section, order
title description section order
Configuration Read layered configuration with compass, from plugin defaults through per-environment files and SUMMER_ variables to runtime overrides saved to disk. services 10

Configuration

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:

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.

Warning

overrides.yaml is written by the running application. Keep it out of version control, and keep secrets in environment variables rather than in values the application persists.