Configuration management workflow
Drupal keeps a site’s configuration in the active store (the database), and the configuration management workflow moves that configuration as YAML files between environments. This page covers exporting active config, committing it, importing it on another environment, and the UUID and schema rules that decide whether an import is allowed to run.
Get a site’s configuration out of the database as files you can commit to version control, and apply those files to another copy of the same site (a staging or production environment). Use this for one site’s own development-to-production pipeline. To package configuration as a reusable building block that other sites can apply, use a recipe instead; see Recipes and config actions.
Prerequisites
Section titled “Prerequisites”- A Drupal 11 site with Drush installed.
- Two environments that are copies of the same site (they share a site UUID; see below).
- An understanding of the difference between simple config and config entities. See Config, content, state, and sessions.
How configuration storage works
Section titled “How configuration storage works”There are two configuration stores, and the workflow moves data between them:
- The active store is where Drupal reads configuration at runtime. By default it is the
configtable in the database. - The sync store is a directory of YAML files, one per config object, named after the config object (
system.site.yml,user.role.editor.yml). Its location is$settings['config_sync_directory']insettings.php. Keep it outside the web root.
Export reads the active store and writes the sync directory. Import reads the sync directory and writes the active store. The StorageComparer computes the difference between the two, and the ConfigImporter applies it. You commit the sync directory to version control; that is the configuration that travels between environments.
Point the sync directory at your project
Section titled “Point the sync directory at your project”Set the sync directory in web/sites/default/settings.php, so it sits next to composer.json, outside the web root:
$settings['config_sync_directory'] = '../config/sync';The path is relative to the Drupal root (web/), so this is config/sync in the project root. Without this line, DDEV’s generated settings fall back to sites/default/files/sync, inside the public files directory, where Git ignores it. Confirm with ddev drush status --field=config, which prints ../config/sync.
Export the active configuration
Section titled “Export the active configuration”On the environment where you made changes (usually local development), write the active store to the sync directory:
ddev drush config:export# Short alias: ddev drush cexThis writes one YAML file per config object into $settings['config_sync_directory']. A config entity exports with a uuid key, and config that originated as an extension’s default config also carries a _core.default_config_hash key recording where it came from. Leave both in place; they are part of the object’s identity.
Commit the exported files
Section titled “Commit the exported files”The sync directory is the unit of version control. Commit it like code:
git add config/syncgit commit -m 'Add the editor role and event content type'Reviewing the YAML diff in a merge request is how a team sees exactly what configuration changed, which is the main reason to manage config as files.
Import on another environment
Section titled “Import on another environment”On the target environment, pull the latest code (which includes the updated sync directory), then import from the project root:
vendor/bin/drush config:import# Short alias: vendor/bin/drush cim\Drupal\Core\Config\ConfigImporter first validates the full set of changes, and if validation fails, nothing is applied. Run vendor/bin/drush config:status first to see which objects differ between the sync directory and the active store before you import.
Verify
Section titled “Verify”Confirm the active store now matches the sync directory:
# Reports "No differences between DB and sync directory" when in sync.ddev drush config:statusThen check a specific object you changed, for example the front page setting:
ddev drush config:get system.site page.frontThe site UUID must match
Section titled “The site UUID must match”Configuration import only runs between two copies of the same site. Every Drupal site has a UUID stored in system.site, and on import core compares the UUID in the incoming system.site against the one already in the active store.
StorageComparer::validateSiteUuid() reads system.site from both the source and the target and returns true only when the two UUIDs are identical. If they differ, core logs Site UUID in source storage does not match the target storage. and refuses the import.
Config entities also carry their own per-object uuid. Core uses it to tell a rename from a delete-plus-create: if an incoming object has the same UUID as an existing one under a different name, the change is treated as a rename rather than losing and recreating the entity.
Config schema
Section titled “Config schema”Every configuration object has a schema, declared in a module’s config/schema/*.schema.yml files, that names each key and its data type. Schema is what makes configuration translatable, typed, and validatable. During config import in development, core checks active configuration against its schema and reports mismatches, so a typo in a key name or a wrong data type surfaces as a test failure rather than silent data corruption.
When you add configuration to a module, add its schema in the same change. The config system, the configuration UI, and config actions all rely on schema being present and correct. See the Configuration API topic for the full schema type system.
Variations
Section titled “Variations”- Partial import.
ddev drush config:import --partialimports only the config objects present in the source and leaves everything else in the active store untouched, instead of deleting active config that is absent from the source. Use it to apply a subset of files; a full import is the default and is what keeps an environment a faithful copy of the committed config. - Environment-specific values. Some configuration differs per environment (API keys, error display, a payment gateway’s sandbox mode). Keep those out of exported config and set them as
$configoverrides insettings.php, for example$config['system.logging']['error_level'] = 'hide';, which take effect at runtime without changing the active store. Values that are not configuration at all, such as the reverse proxy address or database credentials, are$settingsentries in the same file, a separate mechanism. Splitting whole config objects per environment (for example, enabling a development module only locally) is the job of the contributed Config Split module; core provides the per-key override mechanism but not the split feature.
Related
Section titled “Related”- Recipes and config actions to package configuration as a reusable building block.
- Config, content, state, and sessions for what counts as configuration in the first place.
- Deployment workflow for where
drush config:importsits in a full deploy. The Configuration API topicon api.drupal.org.
Was this page helpful?
Drupal is a registered trademark of Dries Buytaert.