Drupal's modular system allows for all kinds of additions to be added to your site through plugins, entities and configuration.
Thanks to Drupal's modular configuration system you can also inject custom configuration into existing configuration entities. This allows you to extend the functionality of an existing entity and ensure that any customisation are saved in the system configuration. Any configuration entity can be augmented without changing the existing configuration schema of the entity, which might cause knock on effects to other modules.
If you have spent time with Drupal configuration then you might have seen the addition of a third_party_settings block to your configuration. A good example of this is with the core Shortcut module and the third party settings it adds to the installed themes when the site is installed using the standard install profile. The Claro theme, for example, will have the following configuration out of the box.
third_party_settings:
shortcut:
module_link: true
This allows the Shortcut module to detect if it should show a link on the page when this theme is present on the page. The configuration for this setting then doesn't have to live in a separate configuration entity (which would be exported into a separate file); it can just live with the configuration for the theme and be loaded as part of the theme configuration.
In this article I will look at how to use additional settings to add custom configuration items to an existing configuration entity. I'll also cover a couple of use cases for this technique.
Creating A Configuration Schema File
The first step is to create a configuration schema file, which is perhaps the most difficult part of the process. This is essentially a "mini" configuration schema that has a namespace of "third_party" that will extend the existing configuration namespace that you want to augment.
The schema name should take the form of the following formula:
<configuration entity name>.third_party.<module name>:
type: config_entity
label "Third party settings"
mapping:
<your configuration schema>
For the purposes of this example I will add a third party setting to a content type (i.e. a "node" configuration entity), specially an "Article" content type. As this content type is available in the standard install profile in Drupal it should be present on most sites. The node content entity has a schema definition of "node.type.*", which means that is where we start our third party settings from.
The following schema definition is added to a custom module, called third_party_settings_example, in the directory config/schema. The file is called third_party_settings_example.schema.yml and is where we store any schema settings for modules.
node.type.*.third_party.third_party_settings_example:
type: config_entity
label: "Example settings"
mapping:
text_field:
type: text
label: "Text Field"
This adds the third party settings for a single text field to the content configuration entity.
With that in place we can how start using the settings to augment content configuration entities.
Injecting Settings
Since we are adding schema settings to the node type entity we need to alter the node_type form using a hook_form_FOMR_ID_alter() hook. This is the form we see when we view the "path /admin/structure/types/manage/article" and is used to configure things like preview settings, publishing options, display settings, and menu options.
Here is the code for this hook, which would like in the .module file.
/**
* Implements hook_form_FORM_ID_alter().
*/
function third_party_settings_example_form_node_type_edit_form_alter(&$form, \Drupal\Core\Form\FormStateInterface $form_state, $form_id) {
$entity = $form_state->getFormObject()->getEntity();
if ($entity && $entity->id() === 'article') {
$exampleTextField = $entity->getThirdPartySetting('third_party_settings_example', 'text_field');
$form['example_settings'] = [
'#type' => 'fieldset',
'#tree' => FALSE,
'#title' => t('Third Party Example Settings'),
'#description' => t('For example purposes.'),
];
$form['example_settings']['text_field'] = [
'#type' => 'textfield',
'#title' => t('Example Text Field'),
'#default_value' => $exampleTextField,
];
$form['#entity_builders'][] = 'third_party_settings_example_node_form_builder';
}
}
What we do here is:
- Ensure that we are looking at an "article" content entity type.
- Fetch the existing third party setting for "text_field" for our module.
- Add additional fields to the content entity form to allow this setting to be changed.
The final step here is to inject a custom called "third_party_settings_example_node_form_builder" callback into the '#entity_builders' form setting, which will be triggered when the form is saved. This custom callback is used to save the added third party settings to the configuration, or clear them if the setting is empty.
/**
* Entity builder for the node:article entity.
*/
function third_party_settings_example_node_form_builder($entity_type, $entity, &$form, \Drupal\Core\Form\FormStateInterface $form_state) {
if ($form_state->getValue('text_field')) {
$textFieldValue = $form_state->getValue('text_field');
$entity->setThirdPartySetting('third_party_settings_example', 'text_field', $textFieldValue);
return;
}
$entity->unsetThirdPartySetting('third_party_settings_example', 'text_field');
}
With this in place we can edit the Article content type configuration page inject some text into the configuration entity.
There are actually a few methods involved with getting and manipulating third party settings, all of which are defined in the Drupal\Core\Config\Entity\ThirdPartySettingsInterface interface. Since the core Drupal\Core\Config\Entity\ConfigEntityInterface extends this interface and that interface is used extensively in Drupal, many different configuration entities have access to these methods.
- setThirdPartySetting($module, $key, $value) - Sets the value of a third-party setting.
- getThirdPartySetting($module, $key, $default = NULL) - Gets the value of a third-party setting.
- getThirdPartySettings($module) - Gets all third-party settings of a given module.
- unsetThirdPartySetting($module, $key) - Unsets a third-party setting.
- getThirdPartyProviders() - Gets the list of third parties that store information.
This is all that is needed to get the system working, so let's look at what happens when we export the configuration with this setting in place.
Exporting Third Party Configuration
When we export our augmented configuration Drupal will detect the presence of our third party settings and inject it into the resulting configuration schema.
Here is the new configuration file for the Article content type (stored in the file node.type.article.yml).
uuid: ebc525b7-eeb3-4a86-9113-4a9019744a64
langcode: en
status: true
dependencies:
module:
- menu_ui
- third_party_settings_example
third_party_settings:
menu_ui:
available_menus:
- main
parent: 'main:'
third_party_settings_example:
text_field: 'Some example text.'
_core:
default_config_hash: AeW1SEDgb1OTQACAWGhzvMknMYAJlcZu0jljfeU3oso
name: Article
type: article
description: 'Use <em>articles</em> for time-sensitive content like news, press releases or blog posts.'
help: ''
new_revision: true
preview_mode: 1
display_submitted: true
As you can see in this example, we have already used the Menu UI module to inject some configuration into the third_party_settings section. Our module updates this section by adding it's own configuration.
Usage
We have now added custom configuration to our Article content entity, but how can we make use of this?
Since this change effects the configuration entity we need to extract this from the content entity to make use of it. This can be done using the content entity as it stores information about the configuration entity that defined it.
As an example, the following implementation of hook_preprocess_HOOK() will inject some content into the Article content entity. We extract the third party setting for our module from the configuration entity connected to our items of content and inject it into the theme content.
/**
* Implements hook_preprocess_HOOK().
*/
function third_party_settings_example_preprocess_node(&$variables) {
/** @var \Drupal\node\NodeInterface $node */
$node = $variables['elements']['#node'];
if ($node->bundle() == 'article') {
$additionalSetting = $node->type->entity->getThirdPartySetting('third_party_settings_example', 'text_field');
$variables['content']['additional_title'] = [
'#markup' => $additionalSetting,
];
}
}
With this in place we will see the text we added to the article configuration entity appear on all Articles. This is a somewhat useless example, but it shows the technique in action. The setting for the text_field was only added to one place, but we have changed how all Articles are displayed across the site.
Note that the decision to add checks for the "Article" content entity here is arbitrary and is used to add to the example. We could build this module in the same way without checking for the Article content type, at which point all content entities would get this setting and gain the ability to have text injected into them like this.
Modules That Use Additional Settings
Here are a couple of modules that make use of third party settings to store configuration within other configuration entities. If you want to see this technique in action then these modules are a good place to start.
Scheduler
The Scheduler (scheduler) module is a commonly used module that allows nodes to be published a certain times. Scheduler stores some configuration for each content type in third party settings, but also allows media items, commerce products and taxonomy terms to be configured in the same way.
MaxLength
MaxLength (maxlength) module allows character limits to be set on text fields; which also includes a character count. The settings for this module are injected into the field configuration for entities and so can be added to any text field on the site.
Allowed Formats
Allowed Formats (allowed_formats) is a module gives sites the ability to hide the often confusing information about text input formats that appear below text fields. This is another field level configuration item that can be added to any text field on the site. It's worth looking into field level third party settings as they have a complex schema setup.
Conclusion
The third party setting technique discussed here has lots of applications, especially if you want to export your settings along with the configuration of your site. Small amounts of configuration items can be easily injected to any configuration entity, which will live with your site. and can be deployed just like any other configuration item.
Third party settings aren't available to all configuration entities, but if the class implements the interface Drupal\Core\Config\Entity\ThirdPartySettingsInterface then it should have this ability.
Whilst this technique is useful, you don't have to use the third party settings system every time. In fact, if your custom configuration alters lots of different types of entities or has lots of custom configuration then you should probably look at storing your configuration in separate configuration files. Whilst third party settings live in a separate namespace within a configuration entity, you should still respect that parent entity and try not to alter it with lots of data if that can be helped.
Also remember that third party settings live with your configuration entities and not your content entities. This means that any settings you add will effect all instances of that content entity. If you want to add custom configuration to individual entities (eg. certain articles, or taxonomy terms) then your should probably use the field API to do this.
I made use of this technique on a recent project by adding additional settings to a Message Template configuration entity (from the Message module). I needed a way to add some additional data to some Message entities, without adding separate fields for each type of Message Templates, and third party settings allowed for this. If the additional data was present on a Message Template configuration entity then we performed additional actions as we created the Message, otherwise the Message was created without performing any extra steps. The additional configuration could then be exported and deployed using the Drupal configuration workflows easily.
All of the code I have used in this article is available in an example third party settings Drupal module on GitHub. Feel free to use this module to create your own third party settings.
There is also a documentation page on Drupal.org that looks at this technique in relation to adding settings to a custom entity that might be worth a read if you want more context about this technique.
Comments
Hi, I saw a typo in your article (just stuck out as I read this), here:
Thanks for the article though, I'm looking to implement something similar in a project I'm working on right now. Good stuff!
Submitted by AO on Mon, 09/04/2023 - 22:37
PermalinkThanks AO! I've corrected that typo :)
Submitted by giHlZp8M8D on Mon, 09/04/2023 - 22:38
PermalinkAdd new comment