development icon

Best Practices for CMI and Features in Drupal 8

Mike Potter, Software Architect
#Content Management | Posted

Last year I blogged about our first Alpha release for Features in Drupal 8 and gave several presentations on Features for D8 at various DrupalCons and camps.  Since Drupal 8.0 (and now 8.1!) have been released in the past few months, I’ve been getting questions on the best way to use Features and Configuration Management (CMI) in Drupal 8.

You’ll have a chance to ask me those questions in person at my Features BOF at DrupalCon New Orleans. But in the meantime, here are some “best practices” for using Features and CMI effectively.

“Do I use Features, CMI, or both?”

It’s great that Drupal 8 has proper configuration management built into core.  This provides a consistent framework, storage, and API for all configuration settings: views, content types, fields… anything that isn’t content.  However, CMI doesn’t provide any way to organize this vast amount of configuration.  If you use CMI to export your site config you’ll see dozens of *.yml files.  That’s great for deploying the entire site config to another site, but what if you want to organize these files in some way?  That’s what the Features module will help you do.

Man talking to a group of people at a technology conference

Features is designed to organize configuration based on analyzing its dependencies.  For example, if you create a custom content type and add some fields, Features will suggest that you group the content type and fields together into a single “feature package.”  A “package” is just a normal Drupal module that has specific configuration (*.yml files) assigned to it.

You’ll want to use Features to create these packages, but once they are created you can add them to your site as regular modules.  Thus, you don’t need the Features module on your production server (normally… more on that later).

“How do I decide what config to package into a feature?”

New to Features in Drupal 8 are Bundles and Assignment Plugins.  A “Bundle” is just a collection of feature packages with a namespace prefix that belong together.  Think of a “bundle” like a “category” of modules.  For example, if I’m creating features for a client named “acme,” I should create a bundle called “acme” so each of my exported packages will start with the acme_* prefix.  This namespacing ensures your packaged modules don’t conflict with any other Drupal module.

Once you have created a Bundle, you can then decide on some business rules to determine how you want to automatically organize your configuration.  These organization rules are controlled by the “Assignment Plugins.”  Each plugin is responsible for a certain ruleset.  For example, the “Base Type” assignment plugin determines the base configuration type to use for the first level of organization and defaults to “Content Type.”  This means Features will suggest one package per content type.  If you have a content type called “Event,” Features will suggest a package called “Event” with the module name of acme_event (in this example).  

Other assignment plugins add additional configuration to your package.  For example, the “Dependency” plugin automatically adds any configuration that depends on the content type, such as fields.  The “Forward Dependency” plugin automatically adds any configuration that itself is a dependency of already added config, such as the field_storage for a specific field.

The list of which plugins are enabled, which order they are applied, and any specific options for each plugin are stored within your Bundle.  Thus, by switching between different bundles you can change the rules Features uses to suggest new package exports. In fact, these bundle settings themselves are stored as “config” and can be exported to a feature.  Once you have defined your business rules for auto-organizing your config, it’s useful to export your bundle configuration and share it between your developers so everybody is using a common ruleset.

Here is the full set of assignment plugins and what they do:

Packages – Detect and add existing package modules.

Exclude – Exclude configuration items from packaging by various methods including by configuration type.

Base type – Use designated types of configuration as the base for configuration package modules. For example, if content types are selected as a base type, a package will be generated for each content type and will include all configuration dependent on that content type.

Namespace – Add to packages configuration with a machine name containing that package’s machine name.

Optional type – Assign designated types of configuration to the ‘config/optional’ install directory. For example, if views are selected as optional, views assigned to any feature will be exported to the ‘config/optional’ directory and will not create a dependency on the Views module.

Forward dependency – Add to packages configuration on which items in the package depend.

Core type – Assign designated types of configuration to a core configuration package module. For example, if image styles are selected as a core type, a core package will be generated and image styles will be assigned to it.

Site type – Assign designated types of configuration to a site configuration package module. For example, if image styles are selected as a site type, a site package will be generated and image styles will be assigned to it.

Profile – Add configuration and other files to the optional install profile from the Drupal core Standard install profile. Without these additions, a generated install profile will be missing some important initial setup.

Existing – Add exported config to existing packages.

Dependency – Add to packages configuration dependent on items already in that package.

You can also write your own plugins if you have set of rules for organizing your configuration that are not covered by any existing plugin (and please contribute it if you can).

Once your assignment plugins are set up (or just using the defaults, which are good rules for most people), you can easily organize your configuration by just selecting All packages from the main Features listing and export them.  You won’t need to mess around in the detailed Edit screen like you did in Drupal 7 unless you need to make specific changes to a package.

“How do I update configuration that has changed?”

Once you have organized your configuration into package modules and enabled them on your site, making changes to this configuration can sometimes be tricky.  For example, let’s say you have exported a View to a feature package.  Now somebody tells you to change the Title of the view.  Go ahead and make this change in the UI on your development machine.  Now when you go to the Features UI page you’ll see that the package containing that view is marked as “Changed.”  You can click on “Changed” to see the exact changes that are detected.  You then have to make a choice:  1) update the exported package module with the change, or 2) undo the change and restore the previous value.

A man speaking to a group of people at a technology conference, sitting next to a monitor.

In Drupal 7 we called 1) update, and 2) revert.  In Drupal 8 we call these 1) export, and 2) import.  There is no way for Features to magically know which action you want to take.  Is this a designed change that needs to be made to your module to fix a bug, or was this a change made by the client via the UI that needs to be preserved or maybe undone?

It’s important to understand that this really doesn’t have anything to do with “deploying” your configuration.  Drupal 8 is still designed with the assumption that configuration is “owned by the site.”  

“What causes the pre-existing configuration error?”

If you decide to update your package module and then push it to production and try to disable it and re-enable it to reload your new configuration, you might be surprised you cannot re-enable the module because it contains “pre-existing configuration.”  This simply means that the site already contains active configuration that is also provided by the module and Drupal won’t allow a module to be enabled that has existing configuration.

This won’t happen in our simple View example because disabling the module providing the view causes the view to be removed from your active config.  You’ll can re-enable the module without problem and will see your new view title.

However, if your package contains a field, disabling the module will not remove the field because this could cause loss of content on your site.  Thus when you try to re-enable the module you’ll get the “pre-existing config” exception.

There are three solutions to this problem:

  1. Enable Features module on your site.
    If the Features module is enabled, it will detect that you are trying to enable a package that contains pre-existing config and it will allow Drupal to continue and will mark that config as “changed.”  You can then use the feature-import command (in UI or via drush) to import the updated configuration and overwrite the value in the current active store.
  2. Write an update hook for your config.
    If you don’t want to enable Features on your site, you should write a normal update hook that will load the config that has changed and save it directly into the active store.  Just be sure you don’t disable your package first or you won’t be able to re-enable it.  Keep the existing package enabled and rely on running update hooks to update the config.  Of course this requires writing custom PHP code.
  3. The Preferred solution is to use CMI to deploy configuration.
    The way Drupal 8 is designed to work is to export and import your entire site configuration.  You should create a “staging” or “test” server that is identical to your production server.  Enable the Features module on this staging server and update or import your packages there and get it all working properly.  Once working, export the entire config using CMI, then import the entire config into your Production server.  If a future update is needed, again make the changes on staging and then do a full export/import on Production.  This solution provides the maximum stability of your Production server and keeps development modules such as Features on your dev and staging environments.

“What if I prefer using Features instead of CMI?”

Drupal 8 and CMI was designed to provide the most common deployment workflows of all Drupal sites, not just “Enterprise” sites.  Even small sites benefit from a separation between a stable Production environment and a Development environment.  Features was built in Drupal 8 to support this default use case as just a development tool.

However, if you prefer the old Drupal 7 workflow for managing configuration via Features you can still do that if you wish.  In this case you’ll update your package modules and push them into your code repo.  Then on Production you’ll pull the new code and then “features revert-all”.  In Drupal 8 this is called “import-all” and is available in the latest beta version as a drush command just like in Drupal 7 (“drush fra -y” still works).  Just be aware that importing all your features on production runs the risk of problems because you are not ensuring that your production site completely matches your test site.

Summary

As great as CMI is in Drupal 8, it is likely that you will still want to use Features to organize your configuration as you develop your site.  Whether you use Features or CMI (or both) to deploy your development into production is your choice.  The new Bundles and Assignment Plugins will help automatically suggest organization of your configuration making Features a lot easier to use for many people.

Features for Drupal 8 is currently available as a stable beta release with a reasonable amount of test coverage.  Once we have more complete test coverage we’ll release the stable version but sites should feel free to start using the beta version now and see how it helps organize your configuration.  Suggestions and bug reports are always welcome in the drupal.org issue queue.

Mike Potter

Mike Potter

Software Architect