Drupal Configuration Management: Exporting, Deploying, and Synchronizing YAML with Drush
Drupal's CMI exports site structure to YAML for version control. Learn the export-commit-import workflow with Drush, plus limits around UUIDs, secrets, and content.
05 Aug 2025, 16:19 UTC

Drupal stores site structure—content types, views, field settings, block placement—in the database, which makes moving changes between development and production fragile and hard to review. The practical fix is Drupal's Configuration Management System (CMI): it exports that structure into human-readable YAML files in a sync directory, lets you commit them to version control, and applies them to another environment with a single Drush command. The core workflow is to export in development, commit the YAML, deploy the code, then import on the target site; Drupal computes the diff and applies creates, updates, and deletes in one transaction.
How CMI Maps Site Structure to YAML
Configuration entities (node types, views, vocabularies, system settings) are the objects CMI manages. Each has a UUID that must remain stable across environments. When you export, Drupal writes each entity to a YAML file in the configurable sync directory. The default location is sites/default/files/config_/sync, but production sites often override this in settings.php to keep YAML outside the webroot. Content entities—nodes, users, taxonomy terms—are not managed by CMI; they require the Migrate API or custom deployment scripts.
A Worked Export and Import Cycle
A typical setup starts by pointing the sync directory to a path under version control. In settings.php, add:
$settings['config_sync_directory'] = '../config/sync';
Run this on every environment so they all read from the same relative location; adjust the path to match your deployment layout. In development, from the Drupal project root and as a user with write access to that directory, export the active configuration:
drush config:export
Drush writes files such as system.site.yml, core.entity_form_display.node.article.default.yml, and views.view.frontpage.yml into the sync directory. Each file contains a uuid key and a _core property identifying the config entity type. Commit these files to Git.
On the target environment, after deploying the code and running any database updates (drush updatedb), preview the changes before applying them:
drush config:import --preview=diff
The preview shows a unified diff of what will be created, updated, or deleted. Because import applies deletions, any configuration object present in the active store but absent from the sync directory is removed. When you are ready, run the import:
drush config:import
Drupal compares the active configuration in the database with the YAML files and applies the differences in a single transaction. If any file fails validation, the entire import rolls back. This workflow applies to Drupal 10/11 with Drush 12+.
Environment-Specific Overrides with config_split
Not all configuration should travel from development to production. The contrib config_split module lets you define named splits—for example, a dev split—that live in separate directories. When the split is active on a given environment, its YAML is merged into the active configuration; when inactive, it is excluded from export and import. This is the standard way to keep development-only modules such as Devel out of the production sync directory.
Limits and Common Mistakes
- Content is not configuration. Nodes, users, and taxonomy terms are content entities. CMI will not move them between environments.
- UUID mismatches break imports. Never manually edit UUIDs in YAML. On a fresh site, import the full configuration set once to establish matching UUIDs; subsequent imports only process deltas.
- Secrets leak into version control. API keys and passwords stored in configuration YAML become visible in Git. Use the
keymodule, environment variables loaded insettings.php, orconfig_splitto keep sensitive values out of the main sync directory. - Uninstalling a module deletes its config. On the next import, configuration belonging to an uninstalled module is removed. If you need to disable a module temporarily without losing its settings, move that configuration to a
config_splitsplit or manage it manually. - Schema-less custom config is fragile. Custom modules should declare a schema file at
config/schema/mymodule.schema.yml. Without it, configuration objects lack typing, translation support, and validation, which can cause silent data loss during multilingual imports. - Concurrent edits cause conflicts. Changing configuration on multiple environments without coordination produces YAML merge conflicts that Drush cannot auto-resolve. Treat one environment as the single source of truth.
Verifying the Result
After an import, confirm synchronization with:
drush config:status
If the command reports no pending differences, the active configuration matches the YAML files. If it lists changes, inspect them with drush config:diff before deciding whether to export from the current environment or re-import from the sync directory.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.