Drupal - What is the appropriate way to store arrays of information with configuration API?

You define a schema type sequence:

Example: /core/modules/options/config/schema/options.schema.yml

# Schema for the configuration files of the Options module.

field.storage_settings.list_integer:
  type: mapping
  label: 'List (integer) settings'
  mapping:
    allowed_values:
      type: sequence
      label: 'Allowed values list'
      sequence:
        type: mapping
        label: 'Allowed value with label'
        mapping:
          value:
            type: integer
            label: 'Value'
          label:
            type: label
            label: 'Label'

You'll find the form for this example in /core/modules/options/src/Plugin/Field/FieldType/ListItemBase.php

More info https://www.drupal.org/docs/8/api/configuration-api/configuration-schemametadata


I was recently struggling how to store data with a nested array of strings,

allowed_view_modes:
  image:
    embed: embed
    full: full

The only way I could figure out how to validate it was by running functional tests, where it validates the configs when you save a config entity.

I was able to save data like the above data by nesting 'sequences':

ckeditor.plugin.drupalmedia:
  type: mapping
  label: 'Media Embed'
  mapping:
    allowed_view_modes:
      type: sequence
      label: 'Allowed View Modes'
      nullable: true
      sequence:
        type: sequence
        nullable: true
        label: 'View Mode'
        sequence:
          type: string

This worked and passed the config linter.

The top level sequence is for the media type, the inner sequence is for each view mode. There are lots of complex examples in drupal core. Unfortunately, I don't know where to point you for the documentation. I've mostly been learning this by trial and error.


This was custom openid_connect plugin I was working on, so a bit different than a ConfigFormBase normal configuration form, but I hope this methodology applies and is helpful to someone in the future. I used a schema like so (snippet):

# Schema for the configuration files of the OpenID Connect module.
openid_connect.settings.nimble:
  type: config_object
  label: 'OpenID Connect Nimble settings'
  mapping:
    enabled:
      type: boolean
      label: 'Enable client'
    settings:
      type: mapping
      mapping:
        roles_membership_types:
        type: sequence
        label: 'Role Membership Types'
        sequence:
          type: mapping
          label: 'Roles mapped to membership types'
          mapping:
            role:
              type: string
              label: 'Role id'
            membership_type:
              type: string
              label: 'Membership Type'

FAPI setup like so (I had NO form index for the actual name of the config item, roles_membership_types:

    $roles = user_role_names(TRUE);
    if (isset($roles['authenticated'])) {
      unset($roles['authenticated']);
    }
    $membership_types = ['' => 'None'] + $this->getMembershipTypes($this->configuration['instance']);
    $roles_membership_types = $this->configuration['roles_membership_types'];
    if (!empty($roles_membership_types)) {
      foreach ($roles_membership_types as $type) {
        $roles_membership_types_default_values[$type['role']] = $type['membership_type'];
      }
    }
    foreach ($roles as $rid => $role) {
      $form['roles_mapped_membership_types'][$rid] = [
        '#title'         => $role,
        '#type'          => 'select',
        '#options'       => $membership_types,
        '#default_value' => !empty($roles_membership_types_default_values[$rid]) ? $roles_membership_types_default_values[$rid] : '',
      ];
    }

Little bit of code in the submit handler to get the values from the role form elements, and put it together in the format necessary to map the config.

$values = $form_state->getValues();
if (!empty($values['roles_mapped_membership_types'])) {
  foreach ($values['roles_mapped_membership_types'] as $rid => $membership_type) {
    $roles_to_membership_type_value[] = ['role' => $rid, 'membership_type' => $membership_type];
  }
$form_state->setValue('roles_membership_types', $roles_to_membership_type_value);
}

So eventually this code would work:

// Save plugin settings.
$this->configFactory()
  ->getEditable('openid_connect.settings.' . $plugin_id)
  ->set('settings', $subform_state->getValues())
  ->save();