Profile picture for user admin
Daniel Sipos
15 Jun 2015

In this article we are going to look at how to use the ThirdPartySettingsInterface to add some extra data to existing configuration entities. For example, if you ever need to store some config together with a node type or a taxonomy vocabulary, there is a great way to do so using this interface. Today we are going to see an example of this and add an extra field to the menu definition and store the value in this way.

There are a number of steps involved in this process. First, we need to alter the form with which the entity configuration data is added and saved. In the case of the menu entity there are two forms (one for adding and one for editing) so we need to alter them both. We can do something like this:

/**
 * Implements hook_form_alter().
 */
function my_module_form_alter(&$form, \Drupal\Core\Form\FormStateInterface $form_state, $form_id) {
  if ($form_id === 'menu_add_form' || $form_id === 'menu_edit_form') {
    my_module_alter_menu_forms($form, $form_state, $form_id);
  }
}

Inside this general hook_form_alter() implementation we delegate the logic to a custom function if the form is one of the two we need. Alternatively you can also implement hook_form_FORM_ID_alter() for both those forms and delegate from each. That would limit a bit on the function calls. But let's see our custom function:

/**
 * Handles the form alter for the menu_add_form and menu_edit_form forms.
 */
function my_module_alter_menu_forms(&$form, \Drupal\Core\Form\FormStateInterface $form_state, $form_id) {
  $menu = $form_state->getFormObject()->getEntity();
  $form['my_text_field'] = array(
    '#type' => 'textfield',
    '#title' => t('My text field'),
    '#description' => t('This is some extra data'),
    '#default_value' => $menu->getThirdPartySetting('my_module', 'my_text_field'),
    '#weight' => 1
  );

  if (isset($form['links'])) {
    $form['links']['#weight'] = 2;
  }

  $form['#entity_builders'][] = 'my_module_form_menu_add_form_builder';
}

In here we do a couple of things. First, we retrieve the configuration entity object which the form is currently editing. Then, we define a new textfield and add it to the form. Next, we check if the form has menu links on it (meaning that it's probably the edit form) in which case we make its weight higher than one of our new field (just so that the form looks nicer). And last, we add a new #entity_builder to the form which will be triggered when the form is submitted.

The getThirdPartySetting() method on the entity object is provided by the ThirdPartySettingsInterface which all configuration entities have by default if they extend from the ConfigEntityBase class. With this method we simply retrieve a value that is stored as third party for a given module (my_module in this case). It will return NULL if none is set so we don't even need to provide a default in this case.

Let us now turn to our #entity_builder which gets called when the form is submitted and is responsible for mapping data to the entity:

/**
 * Entity builder for the menu configuration entity.
 */
function my_module_form_menu_add_form_builder($entity_type, \Drupal\system\Entity\Menu $menu, &$form, \Drupal\Core\Form\FormStateInterface $form_state) {
  if ($form_state->getValue('my_text_field')) {
    $menu->setThirdPartySetting('my_module', 'my_text_field', $form_state->getValue('my_text_field'));
    return;
  }

  $menu->unsetThirdPartySetting('my_module', 'my_text_field');
}

Inside we check if our textfield was filled in and set it to the third party setting we can access from the config entity object that is passed as an argument. If the form value is empty we reset the third party setting to remove lingering data in case there is something there.

And that's pretty much it for the business logic. We can clear the cache and try this out by creating/editing a menu and storing new data with it. However, our job is not quite finished. We need to add our configuration schema so that it becomes translatable. Inside the /config/schema/my_module.schema.yml file of our module we need to add this:

    system.menu.*.third_party.my_module:
      type: mapping
      label: 'My module textfield'
      mapping:
        my_text_field:
          type: text
          label: 'My textfield'

With this schema definition we are basically appending to the schema of the system.menu config entity by specifying some metadata about the third party settings our module provides. For more information on config schemas be sure to check out the docs on Drupal.org.

Now if we reinstall our module and turn on configuration translation, we can translate the values users add to my_text_field. You go to admin/config/regional/config-translation/menu, select a menu and when translating in a different language you see a new Third Party Settings fieldset containing all the translatable values defined in the schema.

Hope this helps.

Profile picture for user admin

Daniel Sipos

CEO @ Web Omelette

Danny founded WEBOMELETTE in 2012 as a passion project, mostly writing about Drupal problems he faced day to day, as well as about new technologies and things that he thought other developers would find useful. Now he now manages a team of developers and designers, delivering quality products that make businesses successful.

Contact us

Comments

Teemu 18 Feb 2016 22:28

Mistake?

I think the last line of the function my_module_form_menu_add_form_builder should start $menu not $type?

Daniel Sipos 04 Apr 2016 16:12

In reply to by Teemu (not verified)

Thanks for the tip. Sorry for

Thanks for the tip. Sorry for the delay.

Ajay Reddy 28 Jul 2016 09:32

Unexpected error

The website encountered an unexpected error. Please try again later.

TypeError: Argument 2 passed to menu_per_user_form_menu_add_form_builder() must be an instance of Drupal\system\Entity\Menu, instance of Drupal\menu_link_content\Entity\MenuLinkContent given in menu_per_user_form_menu_add_form_builder() (line 53 of modules/custom/menu_per_user/menu_per_user.module)

Using same code above.
Drupal 8.1.6

Nikita Pogrebnyak 30 Aug 2016 11:35

Useful Article

Hi Danny,
Thanks for the useful article! I've added an entity_autocomplete field, and if someone interested, the '#default_value' for 'entity_autocomplete' has to be either an entity object, or an array of entity objects (for multi-value field). Here's an example:

/**
 * Handles the form alter for the menu_add_form and menu_edit_form forms.
 */
function menu_series_connector_alter_menu_forms(&$form, \Drupal\Core\Form\FormStateInterface $form_state, $form_id) {

  // Load menu from $form_state.
  $menu = $form_state->getFormObject()->getEntity();

  // Get ThirdPartySetting from the menu.
  $seriesId = $menu->getThirdPartySetting('menu_series_connector', 'series_connector');

  // Add Entity Autocomplete field.
  $form['series_connector'] = array(
    '#type' => 'entity_autocomplete',
    '#target_type' => 'node',
    '#title' => t('Connect Menu to Specific Series'),
    '#description' => t('Connect Menu to Specific Series'),
    '#selection_settings' => array(
      'target_bundles' => array('series'),
    ),
    '#default_value' => (isset($seriesId) && is_numeric($seriesId)) ? \Drupal\node\Entity\Node::load($seriesId) : NULL,
    '#weight' => 1
  );

  if (isset($form['links'])) {
    $form['links']['#weight'] = 2;
  }

  $form['#entity_builders'][] = 'menu_series_connector_form_menu_add_form_builder';
}
Nahtan 22 Mar 2017 17:28

Great example

Great example, thanks. This information was hard to find, but I adapted your example to work with node types, and it works well.

Adrian (akozma) 24 May 2017 16:09

Exception found

Indeed! This is a very helpful example.

I tried to implement this for a Config Entity defined by a contrib module without success.

Later, I found out the ThirdPartySettings works only if the entity contains the "config_export" information ( See ConfigEntityType::getPropertiesToExport() )

The solution was to add the "config_export = {..." definition in the given Config Entity annotation.

Hope this information will help you.

24ma13wg 20 Sep 2017 14:06

No option to translate

It worked, except I was not given the option to translate.

Martijn Houtman 12 Apr 2018 16:15

Very good tutorial

I was hoping to find a ConfigEntity to be fieldable as a regular Entity (e.g. using hook_entity_base_field_info()), but this also works fine! Thanks :)

nimoatwoodway 05 Nov 2019 16:06

Made my day

Thanks Danny!

After hours of searching and trying you finally presented me the solution! I needed an additional field on the field settings form.

By the way, great talk at DrupalCon!

All best
Chris

Add new comment