Settings overlay
The settings store (2.12) keeps public business settings in the database and lays them over the configuration at boot, so every consumer of config('services.google.gtm-id') sees the stored value without knowing the store exists. The user side is on Settings store; this page is how it works.
Classes
core/src/Settings/:
| Class | Role |
|---|---|
EvolveSettings | base class on spatie/laravel-settings; each subclass declares group() and a bridge() map from property to [config key, env variable]. Static helpers sources() (where each property resolves from) and awaitsImport() (nothing stored, but the config holds a non-default value) |
TrackingSettings, SeoSettings, SiteSettings, BrandingSettings, CompanySettings, MailSettings (2.18) | the six groups |
SettingsOverlay | reads every stored property and writes the non-null ones into the config |
PackageDefaults | the values the shipped config resolves with no variable set, used by the import to skip defaults |
Storage is the evolve_settings table through the spatie repository named evolve.
When the overlay runs
EvolveServiceProvider::registerSettingsStore() registers the overlay in an app->booting callback: after every provider merged its config, before any provider boots and reads it. It runs on every boot, web and console, on top of a cached config when there is one. It is skipped while config:cache or optimize build the cache, so stored values are never baked into the cache file: a change in the store takes effect on the next request without a rebuild. It is also skipped during package:discover, which runs on build machines without a database.
A null property leaves the config alone. The property values are read in one query and cached under evolve.settings.v1; warm boots run no query. Without the table (before migrate) nothing is applied and a missing marker is cached, which the next migration clears (MigrationsEnded listener calls forget()). evolve:db:copy and the Envoy database tasks forget the cache on the side that received the database (2.12.1).
Resolution and sources
SettingsOverlay::source($configKey) answers where a resolved value comes from: settings, config, or null. configValue() returns what the .env and config files give the key underneath the overlay; the panel uses it for the "inherits …" placeholders. The health endpoint's tracking.sources and mail.sources, the meta of the tracking and mailer checks, and the shadowed-config check use these (a shadowed key that a stored setting resolves is reported under resolved_by_settings and does not warn).
Writing
Since 2.18 the panel writes the store. Http\Livewire\SettingsManager lists the groups it edits in STORED_SETTINGS; inputs are named <group>.<property>. STORED_PROPERTIES limits a group to some properties (branding: panel colour, login image, logo), STORED_RULES adds validation beyond the types, and casts follow the property types by reflection (int for media IDs, bool for switches). storedChanges() validates and saves before the .env part runs, consentCanBeGranted() resolves the posted store values (posted, inherited for "inherit", current when not posted) and refuses opt-in without banner or CMP. Stored keys are removed from the incoming data, so the bridged env variables are never written; an outdated view copy posting them has them dropped by reportDroppedKeys(). Store-only saves skip the config cache rebuild.
Other writes come from evolve:settings:import (core/src/Console/Commands/ImportSettings.php: reads the config before the overlay, skips package defaults unless --include-defaults, casts to the property type, never edits the .env, logs one settings-imported activity) and from code through the settings classes. A property set back to null falls back to the .env.
Adding a setting
Add a nullable property to the group class and a line to its bridge() with the config key it overlays and the env variable the import reads. Register the property's row with a spatie settings migration ($this->migrator->add('tracking.new_property')), as core/database/migrations/2026_09_26_000000_create_evolve_settings_table.php does for the existing ones and 2026_10_13_100000_add_mail_settings for a whole group (use insertOrIgnore semantics, rows may exist). The overlay, the import and the health sources pick the property up from the bridge. To make it editable, add the input <group>.<property> to settings/index.blade.php (with an entry in ViewOverrides::CHANGES), a rule in STORED_RULES when the type is not enough, and for a new group the class in STORED_SETTINGS.