The original purpose of the Features module was to “bundle reusable functionality”. The classic example was a “Photo Gallery” feature that could be created once and then used on multiple sites.
In Drupal 7, Features was also burdened with managing and deploying site configuration. This burden was removed in Drupal 8 when configuration management became part of Core, allowing Features to return to its original purpose.
But, as the primary maintainer of the Features module, I sadly admit that:
“Features does not actually accomplish the goal of creating truly reusable functionality.”
Let’s look more closely the classic “Photo Gallery” example. Export your Gallery content type, your Image field storage and instance, your View and Image Style into a Feature module. You can copy this module to another site and install it to create a Gallery. But what happens if your other site already has an Image field you want to share? What happens when the namespace used for the features on your new site is different from the namespace of your original site? What happens if you want to add the gallery to an existing content type, such as a Blog?
The problem with configuration in Drupal is that it is full of machine names: content types, fields, views, dependencies, etc. You are supposed to prepend a unique namespace to these machine names to prevent conflicts with other modules and project, but that means you are stuck with that namespace when trying to reuse functionality. When you make a copy of the feature module and change all of the machine names, it becomes difficult to update the original feature with any improvements that might be made on the new project.
Basically, your Feature is not actually a reusable component.
Towards the end of Open Atrium development in Drupal 7, we started using an architecture that allowed reusable functionality to be layered across multiple content types. The Related Content feature added Paragraph bundles but had no opinion about which content type you added these paragraphs to. This was accomplished using the Features Template module in D7, which allowed you to create a template of configuration and use it to create multiple instances of that same configuration across multiple content types. Until now, there was no way to reuse configuration like that in Drupal 8.
Introducing: Config Actions
The new Config Actions module helps to solve this problem and provides a replacement for both the Features Template and Features Override modules for Drupal 8. Config Actions is a plugin-driven module that simply does the following:
Load configuration from a source
Transform the config data and perform optional string replacements.
Save the new data to a destination
These actions are read from YAML files stored within your custom module config/actions folder. When your module is enabled, each action is executed, allowing you to easily manipulate configuration data without writing any code. If you want to write code, you can use the Config Actions API to easily manipulate configuration within your own update hooks and other functions.
Let’s take the “Photo Gallery” example and build a template that can be used by Config Actions:
Use Features to export the configuration (content type, fields, views, etc) into a custom module (custom_gallery).
Move the YAML files from the config/install folder into a config/templates folder.
Edit the YAML files and replace the hard-coded machine names with variables, such as %field_name% and %content_type%.
Create a Config Actions YAML file that loads configuration from these template files, performs string replacement for the variables, then saves the configuration to the active database store.
One of the edited feature configuration template files (field.storage.node.image.yml) would look something like this:
[yaml]langcode: en status: true dependencies: module: - file - image - node id: node.field_%field_name% field_name: field_%field_name% entity_type: node type: image ...[/yaml]
The resulting Config Action rule looks like this:
[yaml]replace: "%field_name%": "my_image" "%content_type%": "my_gallery" actions: field_storage: # name of yml file in config/templates folder source: "field.storage.node.image.yml" dest: "field.storage.node.%field_name%" field_instance: source: "field.field.node.gallery.image.yml" dest: "field.field.node.%content_type%.%field_name%" content_type: source: "node.type.gallery.yml" dest: "node.type.%content_type%" view: source: "views.view.gallery.yml" dest: "views.view.%content_type%" ...[/yaml]
Not only does Config Actions perform string replacements within the actual YAML configuration template files, but it also replaces these variables within the action rule itself, allowing you to specify a dynamic destination to save the config.
Enabling the above module will do the same thing as enabling the original Gallery feature, but instead of creating a “gallery” content type, it will create a “my_gallery” type, and instead of a “image” field it will create a “my_image” field, etc.
Reusing a Template
By itself, this isn’t much different from the original feature. The power comes from reusing this template in a different module.
In your “myclient” project, you can create a new custom module (myclient_gallery) that contains this simple Config Action file:
[yaml]replace: "%field_name%": "myclient_image" "%content_type%": "myclient_gallery" plugin: include module: custom_gallery[/yaml] This will cause Config Actions to include and execute the actions from the custom_gallery module created above, but will use the new string replacements to create a content type of “myclient_gallery” with a field of “myclient_image”.
The “custom_gallery” module we created above has become a reusable component, or template, that we can directly use in our own client projects. We can control the exact machine names being used, reuse fields that might already exist in our project, and customize the gallery however we need for our new client without needing to fork the original component code.
If our new client project makes improvements to the core gallery component, the patches to the custom_gallery template module can be submitted and merged, improving the component for future client projects.
Running actions is similar to importing configuration or reverting a Feature: the action plugins manipulate the config data and save it to the active database store. Any additional imports or actions will overwrite the transformed config data. These are not “live” (runtime) overrides, like overriding config in your settings.php file in D7 or using the Config Override module in D8. The configuration stored in the database by Config Actions is the active config on the site, and is available to be edited and used in the Drupal UI just like any Feature or other imported config.
For example, here is a simple “override” action:
[yaml]source: node.type.article value: description: "My custom description of the article content type" help: "My custom help of the article content type"[/yaml]
When the destination is not specified, the source is used. The “value” option provides new config data that is merged with any existing data. This action rule just changes the description and help text for the “article” content type. Simple and easy, no Feature needed.
Config Action Plugins
Plugins exist for changing config, deleting config, adding new data to config, and you can easily create your own plugins as needed.
The Source and Destination options also use plugins. Plugins exist for loading and saving config data from YAML files, from the active database store, or from simple arrays, and you can create your own plugins as needed.
For example, the above “override” action could be rewritten like this:
[yaml]source: [ description: "My custom description of the article content type" help: "My custom help of the article content type" ] dest: node.type.article[/yaml]
This specifies the configuration data directly in the source array and is merged with the destination in the active database store.
Config Actions addresses many different use-cases for advanced configuration management, from templates to overrides. You can use it to collect all of your site help/description text into one place, or to create a library of reusable content components that you can use to easily build and audit your content model. Developers will appreciate the easy API that allows configuration to be manipulated from code or via Drush commands. I look forward to seeing all the the different problems that people are able to solve using this module.