Skip to main content

Blog Post

Unique Config Implementation Using Config Actions

This year at Drupalcon Nashville I had the pleasure of attending Mike Potter's session on Advanced Configuration Management in Drupal 8.

One of his modules he discussed during this presentation was Config Actions. Config Actions allows developers to manage configuration in Drupal in a more developer-centric approach by giving the ability to create and override existing configuration with templates and actions.

Config templates are exactly what they sound like: a Yaml file which acts as a template for configuration with replaceable variables.  

Config actions are more complex. Actions allow for developers to add new config and override existing config by using a selection of plugins, which we will dive more into below.

First, let me present a scenario I have come across a couple of times this year where config actions fit into perfectly:

A client wants to use the Acquia Site Factory platform. Site Factory allows for the creation of a multitude of unique and separate Drupal sites on the fly and bases them on Drupal profiles. Since each site can have a unique configuration stored in their database as they're updated, and sites could be based on different profiles, there cannot be a config/sync directory.

The initial build of these sites can have configuration stored in the profiles' or modules' config/install directories, but as configuration needs to be updated during the development process it becomes increasingly hard to manage site-to-site. For configuration stored within profiles and modules, we use hook_update_N() implementations to read changes to config files and write them to the database. When configuration for modules that are shared across profiles (site types) requires different changes per site one option is to use the config_rewrite module. This can be very handy in many cases and is something that we use in this particular scenario.  

While config rewrite works for most use-cases in this scenario, there are some cases where we can benefit better from using config actions. One example of this is a custom module which provides a custom content entity and also needs to add reference fields to existing node bundles. The configuration for these fields could be added to the profiles or the custom module itself, but using config/install or config/rewrite means it will overwrite the other config/install and config/rewrite used in the profile. The configuration for the field also cannot go in the profile because not every site using the profile will need this module to be enabled. In comes config actions.

First let's set up a custom module which provides a custom entity. I'm not going to go into creating a custom content entity, but if you've done so before the files listed below will look familiar.

Here's an overview of our module:

├── config
│   ├── actions
│   │   ├── actions.entity_display_fields.yml
│   │   ├── actions.entity_form_fields.yml
│   │   ├── actions.node.field_custom_reference.yml
│   └── templates
│   	├── field.field.field_custom_reference.yml
│   	└── field.storage.field_custom_reference.yml
├── my_custom_entity.info.yml
├── my_custom_entity.install
├── my_custom_entity.links.action.yml
├── my_custom_entity.links.menu.yml
├── my_custom_entity.links.task.yml
├── my_custom_entity.permissions.yml
├── my_custom_entity.routing.yml
└── src
    ├── Controller
    │    └── CustomContentEntityListBuilder.php
    ├── Entity
    │   └── CustomContentEntity.php
    ├── Form
    │   ├── CustomContentEntityDeleteForm.php
    │   ├── CustomContentEntityForm.php
    │   └── CustomContentEntitySettingsForm.php
    └── CustomContentEntityInterface.php

Note that there are two directories under the config directory; actions & templates.

Since we are creating a reference field to add to our existing node bundles (let's say we have article and page node types for now) the first step would be to add a field storage config for each entity type the field will be available for. Usually, this would be called something along the lines of field.storage.node.field_custom_reference.yml, and it would live in the config/install directory. Since we want this to be re-usable across multiple entity types and utilize config actions, we are going to create a file within the config/templates directory of our custom module.

field.storage.field_custom_reference.yml:

langcode: en
status: true
dependencies:
  module:
  - '@entity_type@'
  - my_custom_entity
id: '@entity_type@.field_custom_reference'
field_name: field_custom_reference
entity_type: '@entity_type@'
type: entity_reference
settings:
  target_type: custom_entity
module: core
locked: false
cardinality: 1
translatable: true
indexes: {  }
persist_with_no_fields: false
custom_storage: false

 

Since this is a template, we can use variables to replace values when we want to re-use the template.  In this case, we are replacing any reference to an entity_type with @entity_type@.  That's really it for the template.  It's a standard field storage Yaml config file with a few parts replaced with a variable.

Field instance configs get the same template treatment, field.field.field_custom_reference.yml:

langcode: en
status: true
dependencies:
  config:
  - 'field.storage.@entity_type@.field_custom_reference'
  - '@entity_type@.type.@bundle@'
id: '@entity_type@.@bundle@.field_custom_reference'
field_name: field_custom_reference
entity_type: '@entity_type@'
bundle: '@bundle@'
label: 'Custom Entity Reference'
description: 'Connect content to a custom entity.'
required: false
translatable: false
default_value: {  }
default_value_callback: ''
settings:
  handler: 'default:custom_entity'
  handler_settings:
    target_bundles: null
    sort:
      field: _none
    auto_create: false
field_type: entity_reference

To use these templates we need to implement config actions that read from them and replace the variables for the given node types we are updating.

First, we will create an action file for the fields. This combines the usage of the field_storage and the field_instance templates into one actions file, stored in config/actions, actions.node.field_custom_reference.yml:

replace: 
 "@entity_type@": "node"
actions:
  field_storage:
    source: "field.storage.field_custom_reference.yml"
    dest: "field.storage.@entity_type@.field_custom_reference"
  field_instance:
    source: "field.field.field_custom_reference.yml"
    dest: "field.field.@entity_type@.@bundle@.field_custom_reference"
    actions:
      article_action:
        replace:
    	  "@bundle@": article
      page_action:
    	replace:
      	  "@bundle@": page

At the top of the file you can see we have replace key with the @entity_type@ variable from the templates and give that value of "node". Now all actions that fall below will replace @entity_type@ with node.

In the actions key we list two actions, one for adding field storage configs and another for adding field instance configs.  Since we only have one entity type (node) which we are adding field storage for we only need the one action there.  The source key defines where the config should be read from. In this case, we are giving it a .yml file so config_actions knows to read the template we created (config/templates/field.storage.field_custom_reference.yml). The dest key is the configuration destination we are storing this for in the database.

The field_instance action also has a source and destination but also has sub-actions. Each sub-action is a separate bundle we want to add the field instance for. Within each sub-action we are replacing the @bundle@ variable in the templates with the node type's machine name.

At this point we can actually enable our module. When it's enabled, you will see a message on the screen that tells you each config that was created:

- field.storage.node.field_custom_reference

- field.field.node.article.field_custom_reference

- field.field.node.page.field_custom_reference

If you go to the manage fields pages for those content types, you should see the field you defined there.

The next step is to add an action to place the field on the form automatically with config actions. Since we are updating existing configuration, we can just add an action file. No need for a new template.

actions.entity_form_fields.yml:

plugin: add
path: ["content"]
value:
  field_custom_reference:
    type: entity_reference_autocomplete
    weight: "@weight@"
    region: content
    settings:
      match_operator: CONTAINS
      size: 60
      placeholder: ''
    third_party_settings: {  }
actions:
  article_action:
    replace:
      "@weight@": 2
    source: "core.entity_form_display.node.article.default"
  page_action:
    replace:
      "@weight@": 1
    source: "core.entity_form_display.node.page.default"

In this file we have the plugin key. There are several different plugins that can be used in config actions. Here, since we are adding to existing config, we use the "add" plugin. The path key lets us define an array of items which will be used to define where we want our new addition to go in the config. If we wanted to add a dependency in the form display config the array would be ["dependencies", "config"]. Since this is a field being added to an entity form display, we want it to go in the "content" region of the existing configs. The value is what we are going to update the config with.  Here we are adding the field's form display configuration. This is the same configuration you would see if you were exporting existing configuration.

Once again we are adding 2 actions to the file: one for articles and one for pages. The source key in here defines what existing config is going to be updated.

The last step is to add the new field to the displays for our node types. Each display will have its own action, but all display actions will go in the same config actions file actions.entity_display_fields.yml:

plugin: add
path: ["content"]
value:
  field_custom_reference:
    type: entity_reference_label
    weight: 6
    region: content
    label: hidden
    settings:
      link: false
    third_party_settings: {  }
actions:
  article_default_display_action:
    source: "core.entity_view_display.node.article.default"
  article_card_display_action:
    source: "core.entity_view_display.node.article.card"
  page_default_display_action:
    source: "core.entity_view_display.node.page.default"

In the actions, you can see that we are updating two displays for the article content type and one display for the page content type.

Now by enabling our custom module, you'll see all of these configs have been updated!

To read more about config actions check these links:

Chris Runo

Meet team member, Chris Runo

Chris brings four years of Drupal experience to his role as a Senior Drupal Developer at Mediacurrent. Throughout his web development career, Chris has gained a unique perspective on what...

Learn more about Chris >

Related Insights

Access icon Up arrow icon Drupal 8 icon Facebook icon - white Facebook icon - blue outline Facebook icon - yellow Hollow right arrow icon Hollow right arrow icon - white LinkedIn icon - white LinkedIn icon - hollow LinkedIn icon - blue outline LinkedIn icon - yellow Mediacurrent wordmark Quote icon Twitter icon - white Twitter icon - hollow Twitter icon - blue outline Twitter icon - yellow Youtube icon - white Youtube icon - yellow