Files
summercms/docs/services/configuration.md
Jakub Zych 44bd1446f5 feat(11.1-04): add the Backend section, the remaining Services pages and the concept map links
- docs/backend: admin controllers, forms, lists and filters, relation
  manager, users and permissions, settings, partials and widgets, admin SPA
- docs/services: storage, outbound HTTP, realtime, Web Push, search, parity
  testing and the Frontend and AJAX (not provided) page
- Examples for cabana (with testdata/docs YAML), fetchguard, lighthouse and
  its centrifugo driver, flare, beachcomber and typesense, tide; lighthouse
  and beachcomber TestDocs* regions run on their Postgres harnesses
- concept map rows link their guide pages and the not-provided rows the
  Frontend and AJAX page; index lists Backend, Database and Services
- TestDocsRequiredPages asserts the D-08 section order
2026-09-30 23:18:35 +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; see Settings.

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.