# Configuration

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

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](/docs/api/compass.md), with the same dot paths. [Setup, Configuration](/docs/setup/configuration.md) 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:

```go
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](/docs/backend/settings.md).

> [!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.
