watermark
Lego ducks

How a Drupal site works is governed by a series of configuration items stored in the database. Modules and themes can plug into this system and extend it to create their own configuration items. When you change something in a Drupal site it is stored in the configuration so the change is made permanent.

The configuration in Drupal lives in the database, but it can also be exported to the file system and stored as a bunch of files. Changes to these files can then be imported into Drupal in order to change the active configuration on the site. This is a really powerful concept and gives the ability for developers to launch new features on a site by importing altered configuration files. All they need to do is change the configuration, export that change to the file system and deploy the change to the remote server.

The best practice is to export your configuration to the file system outside of the webroot, which means that the configuration files are not visible from the site. The files exported are in a format called YAML, which is a somewhat human-readable format that relies on formatting to nest elements inside other elements.

Once you have installed your Drupal site and are ready to export your configuration you need to do a couple of things. The first is to edit the Drupal settings.php to tell Drupal where your configuration directory is located. The following will be translated into a configuration directory next door to your web directory.

$settings['config_sync_directory'] = '../config/sync';

Note: Prior to Drupal 8.8.0 this setting is slightly different. If you are running an older version of Drupal I suggest you look at the documentation for this setting first.

You’ll need to create this directory if it doesn’t already exist. Your repo should look something like this.

/config/sync/ <- configuration directory.
/web/ <- Drupal installed here.
composer.json
composer.lock

With that in place, you can now enable the Configuration Manager module that controls the import and export of configuration within Drupal. This module allows you to export your configuration directly from the site as a .tar file, but the best approach is to use Drush to perform the export for you. This will take into account the configuration location you supplied and export your configuration there.

To export your configuration use the config:export Drush command.

drush config:export

If you take a look in the config/sync directory you’ll see it is now full of files. As there are over 100 files in a default Drupal install I won’t go into the detail of what each file is here.

Conversely, to import changes to your configuration use the config:import command.

drush config:import

This will apply any changes made to the configuration to the site. Drupal also has some internal sense checking systems to make sure you don’t corrupt the configuration. For example, Drupal won’t let you delete a field or a content type if data exists in those items.

One configuration item I normally point to as an example of how this system works is the basic site settings interface. When you go to the page at /admin/config/system/site-information you'll see a form detailing some default parts of the site like the site name, admin email address and the default 404, 403 and front pages. When the configuration has exported this area is exported as a file called system.site.yml. As it doesn't make sense to talk about Drupal YAML configuration files without adding some YAML into the post here is the contents of the system.site.yml file on an example Drupal site.

uuid: 9aea865a-411b-11eb-b378-0242ac130002
name: 'My Awesome Drupal Site'
mail: noreply@example.com
slogan: ''
page:
  403: ''
  404: ''
  front: /
admin_compact_mode: false
weight_select_max: 100
langcode: en
default_langcode: en
_core:
  default_config_hash: yXadRE77Va-G6dxhd2kPYapAvbnSvTF6hO4oXiOEynI
mail_notification: ''

As you can see from the above the YAML format is pretty readable. It's not best practice to hand edit these forms as there are some internal processes that work out the values of the file and if they have changed, but at least it's possible to read it and see what's in there.

Perhaps the most important part of any Drupal configuration is the workflow surrounding it. The most important tool in this process, by far, is git. I think it’s often overlooked how crucial source control is in the governance of configuration management and I have seen very detailed articles discussing configuration but fail to mention that git should be how the configuration is controlled outside of Drupal.

When you export changes to the configuration then using git makes it clear to see what has been added to your system by looking at the changes. Additionally, any changes you make can be inspected by other developers using the same codebase. They are essentially file changes and so are just as important any code changes. Using git also allows you to learn the configuration system, by altering and exporting configuration you get a good feel for what actions in Drupal change what files in configuration.

With git controlling your configuration you can set about building an automated deployment system that takes the latest version of your code, deploys it and then imports the configuration into your site. Note, if you are building such a system, you’ll need to update the database first and then install the configuration as modules tend to have updates that allow the configuration to be imported cleanly.

Splitting Your Configuration

What I have described so far is fine for a single site, but what happens if you want to have a different configuration in your production environment? You could just accept these differences as the default in all systems but this approach has some problems. For example, let’s say you turned off page caching locally so you work on a module or new feature. When you come to export your configuration you need to be very careful not to commit your page cache difference or you might deploy that to your production site and change that upstream. This situation becomes much more difficult when you introduce things like payment gateways.

You absolutely don’t want to accidentally deploy the sandbox payment system to your live site.

This is where the module Configuration Split comes into play. This module allows you to create splits between your default and a special case that needs to override the configuration in some way.

The configuration split module works by allowing you to set up a bunch of different splits. Each split should be treated as a separation of the configuration from a ‘default’ situation. Within the splits, you will create conditional splits or complete splits depending on your needs.

A conditional split is anything a site does that is different from the default split. This might be the name of the site but could also be things like payment gateways or analytics settings. A complete split is anything that the site does that no other site does. Installing the shield module on the development site or activating single sign-on modules on the production site are examples of this.

Before you set up your splits you’ll need to create a directory structure for it to live in. The following example allows for a default configuration and three other splits for dev, stage and prod.

config/sync <- the default configuration directory
config/dev
config/stage
config/prod

You could also opt to set out the splits like this, which gives a little more visibility to what is the default configuration and what is a split directory.

config/sync <- the default configuration directory
config/splits/dev
config/splits/stage
config/splits/prod

One vital thing to remember is that you can't nest configurations. Drupal gets very confused and breaks in interesting ways if you put your split configurations inside your default configuration.

Next, you need to allow for some way of detecting what environment you are currently using and to activate the configuration on that environment. Here is an example of such a setup that will activate the ‘dev’ configuration split if the dev environment is detected.

if (getenv('DEV') == TRUE) {
  $config['config_split.config_split.dev']['status'] = TRUE;
}
else {
  $config['config_split.config_split.dev']['status'] = FALSE;
}

By running the normal Drush config:export commands configuration split will automatically figure out what split is active and generate the configuration splits according to the split settings you have setup.

The only thing to watch out for here is if you want to change some configuration that has been split across all platforms then you need to ensure that you update the configuration files in all environment directories. If you don’t then you might find that your stage site has a different configuration to the other sites.

You can also use configuration split to split the configuration between sites in a multi-site setup. This follows many of the same rules but needs a little bit more strategy in getting things working seamlessly.

Configuration Ignore

The configuration ignore module allows you to ignore specific configuration items so that they aren’t imported into your configuration. This might sound like a strange thing to do, but it has some very good use cases, especially when there is a fine line between content and configuration.

One use case in particular is the Webform module. Any new Webforms you create on your site are exported into configuration, which means they follow the deployment process without any problems. The issue comes when your users create their own Webforms on the production site. This means you have to export and capture that configuration or Drupal will simply delete the new Webforms when importing configuration as they don’t exist in the configuration files.

Adding an ignore rule around Webforms is a common use case for Configuration Ignore as it means your users are free to build all the contact forms they could ever want without causing you deployment headaches. The Webforms are simply skipped in the configuration import and so are not deleted.

The interface for Configuration Ignore is pretty simple and consists of a large text area that you plug your ignore rules into. To ignore all webform items, but not the other webform configuration items use the following.

webform.webform.*

If you really need to have a specific webform that is controlled by configuration then you can supply an override using the ~ symbol.

~webform.webform.contact

Note that the configuration ignores module works by ignoring configuration as it comes into your site. It doesn’t control the outgoing configuration so I often put .gitignore rules in place to prevent exported files being put back into the codebase.

You can further augment the actions of configuration ignore using the Configuration Split Ignore module to combine it with the Configuration Split module. This module allows you to create configuration ignore rules for modules that are only enabled in certain splits.

Read Only Configuration

Even though you can ignore some configuration settings you certainly can’t get away with ignoring them all. One way to prevent your users from fiddling with your site configuration is to use the Configuration Read-Only module.

This module will lock any form that changes configuration in any way. As most of the admin pages in Drupal change configuration this disables quite a large chunk of the site.

When you first install this module it appears to do nothing. You need to add a line of code to your settings.php file before it becomes active.

$settings['config_readonly'] = TRUE;

Once you’ve done this any form that generates configuration of any kind will be disabled. Your users will still be able to see the form and what it contains but the save buttons will be disabled.

Not only does this work with forms, but it will also prevent Drush from changing the configuration via import. For this reason, it’s a good idea to detect if the site is being accessed via the command line and allow the content to be changed in this situation.

if (PHP_SAPI !== 'cli') {
  $settings['config_readonly'] = TRUE;
}

It’s probably also a good idea to allow the configuration to be changed on your local site so that you can actually build that new feature. Tying the same platform detection into your setup as it used for the configuration split is a great way to allow this.

Thankfully, the module does have a way of allowing your users access to some parts of the configuration using a settings variable. For example, to allow users access to the webform interface (remember that webforms are configuration too and so will also be restricted by this module) add the following to your settings.php file.

$settings['config_readonly_whitelist_patterns'] = [
  'webform.webform.*’,
];

Adding overrides does get a little bit more complicated when the form in question changes different configuration items. The Drupal Account settings page needs the following setup to be in place in order to allow users to change it.

$settings['config_readonly_whitelist_patterns'] = [
  'user.mail',
  'user.settings',
  'system.site',
];

Finding out what configuration settings affect what forms so you can poke holes in the configuration lock can be difficult.

Although this module is really powerful it can be complex to set up and maintain. Unless you are very particular with the setup you will be fielding support requests to allow users to edit pages for quite a while after this module is activated.

You can simplify the administration of this section a little by using the Configuration Read-only filter module. This also has the handy effect of exporting the read-only rules into configuration meaning you can deploy them easily.

Conclusion

I have covered quite a bit here, but the fundamental requirements to tying this all together are git (other source control systems are available) and some form of deployment workflow. As long as those two components are in place then the rest is just the fine-tuning of that process.

Using git allows you to get used to the files that change when you export configuration. If you are new to Drupal and it's configuration system then you should be using a change > export > commit workflow to see exactly what changes you are making to the site and how they affect the configuration files. Thankfully, most of the configuration files are sensibly named and so it's not difficult to see what change produced what files.

Your deployment workflow needs to be more than just deploy code, import configuration as you should also be running any database update hooks before importing. This is because some modules might introduce new configuration items or change the format of configurations and so you need to allow these changes to be applied before you can import configuration on top. As a side note, when you update Drupal and any third-party modules then it's a really good idea to export the configuration to make sure you have captured any changes made to the module configuration.

Using configuration ignore to prevent certain configurations from being overwritten is a good first step to customising your configuration workflow. I normally start with things like Webform and then add other things if they are needed. Think about things that your users change regularly that will cause you problems when deploying over the top of the changes. Modules like Asset Injector will likely be on your radar for this kind of thing.

Using the configuration split module is fundamental to having multi-platform configurations. My best advice here is to have some kind of 'default' state in mind and only deviate from that default when you need to. Not only does this keep the complexity of your splits down but it also helps to reduce the differences between your sites (which is handy for debugging). You'll probably want to turn on the UI and Devel modules on your local setup, but the module allows for you to elect modules as part of the split, so you don't need to split out all of the individual configuration files.

A good tip is to use update hooks to import new configuration splits before you import the configuration. If you don't do this then Drupal will only install your split on the configuration import, it won't actually apply any of the configuration rules. This is another reason why running your database updates first is a good idea as you can leverage the update process to clear the way for the configuration import.

The configuration read-only module definitely has it's place, but you need to be absolutely sure of what you want to restrict or allow before you install it. At first, it seems like a really good idea to lock down anything that is configuration related, but you should probably decide upfront that you want to allow, for example, Webforms to be edited before applying this to your site. It does create a good security model though as even if an attacker got access to the site they would only be able to change content as all of the administration functions would be completely disabled.

In reality, your configuration setup should be taken in a stepped approach. Using configuration ignore is probably a good thing to start with, but using configuration split when it's not really useful can just complicate your setup. I would start a site without configuration split and then only start adding splits when things need to deviate from the default site setup.

This isn't the end of the story for configuration in Drupal. As I write this work is being done on the configuration management 2.0, which introduces features like safer importing of clashing configuration and selective configuration exports. The initiative should solve some of the common problems that Drupal developers face and that the above modules are designed to solve.

A picture of Phil Norton on a blank background, wearing a black t-shirt and a sheepish grin.

Written by

Philip Norton

Developer