Last updated April 13, 2014. Created by Gábor Hojtsy on February 1, 2013.
Edited by Kyna, stBorchert, rpayanm, Sutharsan. Log in to edit this page.

Drupal 8 includes support for a Kwalify (http://www.kuwata-lab.com/kwalify/) inspired schema/metadata language for configuration YAML files. Kwalify itself is written in Python and we needed slight adjustments in the format, so not all of the details of Kwalify are directly applicable, but it is pretty close.

Table of contents

An introductory example #

System module has three configuration settings related to maintenance mode (whether the site is taken offline for normal visitors):

<?php
$systemMaintenance
= \Drupal::config('system.maintenance');
$message  = (string)  $systemMaintenance->get('message');
$isEnable = (boolean) $systemMaintenance->get('enabled');
$langcode = (string)  $systemMaintenance->get('langcode');
?>

The default value of these settings are stored in the core/modules/system/config/system.maintenance.yml file as:

message: '@site is currently under maintenance. We should be back shortly. Thank you for your patience.'
langcode: en
# Note that 'enabled' does not have a default. This is an exception.

Each module can have as many configuration .yml files as needed. All of these are explained in one or more schema files that are shipped with the module. In system module's case the files are at core/modules/system/config/schema. These define the basic types (in system.data_types.schema.yml, see later), but for our examples, the most important is that they define the schema for the maintenance settings (because it is contained in the same module). The corresponding schema section from the system.schema.yml file is as follows:

system.maintenance:
  type: mapping
  label: 'Maintenance mode'
  mapping:
    message:
      type: text
      label: 'Message to display when in maintenance mode'
    langcode:
      type: string
      label: 'Default language'

The top level key ("system.maintenance") in the file refers to the base filename of the .yml file ("system.maintenance.yml") and to the name of the configuration object (config('system.maintenance')). The nested levels describe what is in the file. The type "mapping" is a basic type for key-value pairs. The schema label "Maintenance mode" describes the content of the schema. Then the actual elements are listed under the "mapping" key, where the two items "message" and "langcode" are defined individually. Each element has a "type" and a "label" key which respectively describe the data type and a give a description of the data. The label usually is the same or similar as the configuration form label where the value can edited by the system administrator.

In all practical cases supported by core, the top level item in the .yml file will be a mapping with elements described in a mapping list underneath. Then the individual elements can be of any type based on how you defined the data.

What are schema files used for? #

The primary use case schema files were introduced for is multilingual support. We need to have a tool to identify all translatable strings in your shipped configuration so when you ship with your own settings as well as default Views, additional user roles, menu items, etc. we can offer those up for translation as part of your module/theme release on http://localize.drupal.org. The nesting levels and types would be enough for this use case.

The secondary use case is providing actual translation forms for configuration based on your data as well as exposing translatable configuration pieces to external tools. This use case is where types gain more importance and labels become crucial.

See http://drupal.org/project/config_inspector for module to help with debugging your schemas. The module builds forms among other things based on the schema. An actual end-user module built based on the schemas is "Configuration translation" in core which provides a complete translation user interface for configuration. Built-in translatable types are 'label' for one-line text input and 'text' for multiline text input.

There are most likely other use cases people will find for the schemas.

Properties #

  • type: The type of the value; can either be a base type or a derived type (see examples below).
  • label: User interface label for the value. The label does not have to match a corresponding configuration form label, but matching labels will improve clarity.
  • translatable: Whether the defined type is translatable; it is not suggested to define your own translatable types unless you will integrate with http://drupal.org/project/config_translation to provide a user interface and the core built-in locale module to define how translations are created for the elements; just use the text and label types or derivatives of them.
  • class: Only to be used on base types to assign the class implementing parsing (see above for examples on Typed Data and configuration system defined types).
  • Type specific properties:
    • mapping: Property on value of the mapping type, used to list the underlying elements in the mapping.
    • sequence: Property on value of the sequence type, used to list the underlying elements in the sequence.

Types supported in metadata files #

The most basic types as well as some interesting complex types are defined in core.data_types.schema.yml.

# Basic scalar data types from typed data.
boolean:
  label: 'Boolean'
  class: '\Drupal\Core\TypedData\Type\Boolean'
email:
  label: 'Email'
  class: '\Drupal\Core\TypedData\Type\Email'
integer:
  label: 'Integer'
  class: '\Drupal\Core\TypedData\Type\Integer'
float:
  label: 'Float'
  class: '\Drupal\Core\TypedData\Type\Float'
string:
  label: 'String'
  class: '\Drupal\Core\TypedData\Type\String'
uri:
  label: 'Uri'
  class: '\Drupal\Core\TypedData\Type\Uri'

As can be seen, the most basic data types are mapped to their TypedData API counterparts. This example also shows how easy it is to define your own types. Just define the class that would map to the type. Some more complex types are added by the configuration system for example:

# Basic data types for configuration.
undefined:
  label: 'Undefined'
  class: '\Drupal\Core\Config\Schema\Property'
mapping:
  label: 'Mapping'
  class: '\Drupal\Core\Config\Schema\Mapping'
sequence:
  label: 'Sequence'
  class: '\Drupal\Core\Config\Schema\Sequence'
# Default mapping for unknown types or types not found.
default:
  type: undefined
  label: 'Unknown'

Mapping as shown above is a key-value pair list type ("associative array" or "hash") while Sequence is a simple indexed list ("indexed array"). Types can also simply derive from other types, for example "label", "path" and "text" are all defined as strings. The distinction of these types could help tools parsing the schema to identify textual types for different purposes.

# Simple extended data types:
# Human readable string that must be plain text and editable with a text field.
label:
  type: string
  label: 'Label'
  translatable: true
# Internal Drupal path
path:
  type: string
  label: 'Path'
# Human readable string that can contain multiple lines of text or HTML.
text:
  type: string
  label: 'Text'
  translatable: true

Note that the label and text type are marked as translatable. This means the core interface translation module will identify items with this type and translate based on community or admin provided translations from the database, creating translation override files.

Finally, you can even define complex types based on the simple types by using the format explained above for the maintenance mode:

# Complex extended data types:
# Mail text with subject and body parts.
mail:
  type: mapping
  label: 'Mail'
  mapping:
    subject:
      type: text
      label: 'Subject'
    body:
      type: text
      label: 'Body'

This gives you a reusable "mail" type for email text settings where a subject and body are in a mapping list. This is exactly the same as defining schema for a config key, but you picked a name for it that is not an existing config key, so it will not conflict with other schema definitions. Based on this definition "mail" can be used as a type elsewhere (as is used in user module's email settings schema in user.schema.yml):

user.mail:
type: mapping
mapping:
  cancel_confirm:
    type: mail
    label: 'Account cancellation confirmation'
  password_reset:
    type: mail
    label: 'Password reset email'
  register_admin_created:
    type: mail
    label: 'Account created by administrator'
  register_no_approval_required:
    type: mail
    label: 'Registration confirmation (No approval required)'
  register_pending_approval:
    type: mail
    label: 'Registration confirmation (Pending approval)'
  status_activated:
    type: mail
    label: 'Account activation'
  status_blocked:
    type: mail
    label: 'Account blocked'
  status_canceled:
    type: mail
    label: 'Account cancelled'

Dynamic type references #

As shown above, even simple types are essentially references, and complex types like "mail" are routinely used to reference complex types. Sometimes the type of a value is not static and can depend on the data, such as for image styles that can have different effects applied or views, which consists of various plugins. You can reference keys in the data as part of the type name to refer to dynamic types.

Variable values in types should be enclosed in [] (square brackets), and variable values can be combined with known components. There are three types of references possible:

  1. Element-key reference: such as type: book.[%key] where %key is replaced by the element's key.
  2. Sub-key reference: such as type: 'views.field.[table]-[field]' where the type is computed based on the value of table and field keys in the nested structure
  3. Parent-key reference: such as type: 'views.display.[%parent.display_plugin]' where the display_plugin key from the parent is used to figure out the type for the element

There are rich examples of this in image styles and views which use plugins extensively. An example from image styles considering core/modules/image/config/image.style.medium.yml that has this YAML data structure:

name: medium
label: 'Medium (220x220)'
effects:
  bddf0d06-42f9-4c75-a700-a33cafa25ea0:
    name: image_scale
    data:
      width: 220
      height: 220
      upscale: true
    weight: '0'
    ieid: bddf0d06-42f9-4c75-a700-a33cafa25ea0

Here the structure of the data key depends on the type of the effect, which is specified in the name property of the effect. So the type to be used depends on the data and cannot be specified statically. Differently set up image styles would use different effects. So we need to build in a reference to the type specification. The corresponding schema section from image.schema.yml is as follows:

image.style.*:
  type: mapping
  label: 'Image style'
  mapping:
    name:
      type: string
    label:
      type: label
    effects:
      type: sequence
      sequence:
        - type: mapping
          mapping:
            name:
              type: string
            data:
              type: image.effect.[&#37;parent.name]
            weight:
              type: integer
            ieid:
              type: string

This defines metadata for all image styles (image.style.*) as a mapping of name, label and effects keys. Then effects itself is a a sequence (there can be any number of effects), with each item in the list a mapping with details about the effect. Common values for the effect are name, weight and ieid, while the content of the data depend on the parent's name value (in the above example "image_scale" is the name of the effect used). So when this schema is applied on the data, image.effect.image_scale is the actual type referenced.

Code style to use for schema files #

Just follow the .yml code style as applicable elsewhere in Drupal core. See the above examples for the approach to follow. Key points:

  • Include a top level comment explaining what is in the file. If you only have one schema file for your whole module, a comment like this suffices: # Schema for the configuration files of the Contact module.
  • Avoid comments that provide no extra clarity. Such as "Comment settings" above a section defining schema for comment.settings is superflous. The schema items should have labels anyway, which should describe them well. Only add comments if necessary.
  • Do not use double quotes for strings, use single quotes.
  • Use single quotes for label values even if they are one word for consistency.
  • Never use quotes for key definitions and types (in Drupal, key names and types are strings by definition and should not have spaces).
  • In Drupal, integer values contained YAML config data files are cast to string and therefore are wrapped in single quotes.
  • Add labels to at least the values which will need to be translatable (as well as the containers that wrap them). See the configuration inspector tool detailed below in the debugging section to test whether a form can be generated from your schema in a useful way.
  • Watch your indentation levels. This is not a code style requirement per se, since it is important to use proper indentation in YAML so you get the desired schema structure.

Note: The regular configuration data .yml file style dictates you only use single quotes when more than one word is used because the .yml serialization will do that as a standard practice, so this standard makes diff-ing simpler for changing configuration. However the schema recommendations above differ from that, because schema files are always hand-written and using quotes around label values all the time is better for consistency.

PHP API #

You can retrieve configuration dressed up with the metadata using the \Drupal::service('config.typed') function (such as for the system maintenance mode):

<?php
$definition
= \Drupal::service('config.typed')->getDefinition('system.maintenance');
?>

The structure of the array will be as follows:

<?php
  $definition
= array(
   
'label' => 'Maintenance mode',
   
'class' => '\Drupal\Core\Config\Schema\Mapping',
   
'mapping' => array(
     
'enabled' => array(
       
'label' => 'Put site into maintenance mode',
       
'type' => 'boolean',
      ),
     
'message' => array(
       
'label' =>  'Message to display when in maintenance mode',
       
'type' => 'text',
      ),
    ),
  );
?>

A more complex example to retrieve the typed data associated to the medium image effect's first effect data as cited above in the parent references section:

<?php
// Get typed configuration from under the the image.style.medium config
// key's effects children.
$effects = \Drupal::service('config.typed')->get('image.style.medium')->get('effects');
// Take the ieid key shown above in the example config file (corresponding to the
// first effect in the style) and the data children's definition.
$definition = $effects['bddf0d06-42f9-4c75-a700-a33cafa25ea0']['data']->getDefinition();
?>

This will result in an image.effect.image_scale type as explained above, and will return a structure like:

<?php
  $definition
= array(
   
'label' => 'Image scale',
   
'class' => '\Drupal\Core\Config\Schema\Mapping',
   
'type' => 'image.effect.image_scale',
   
'mapping' => array(
     
'width' => array(
       
'type' => 'integer',
       
'label' => 'Width',
      ),
     
'height' => array(
       
'label' =>  'Height',
       
'type' => 'integer',
      ),
     
'upscale' => array(
       
'label' =>  'Upscale',
       
'type' => 'boolean',
      ),
    ),
  );
?>

The TypedData API can be fully leveraged on the elements. Such as:

<?php
// Get effects on the medium image style (same as above).
$effects = \Drupal::service('config.typed')->get('image.style.medium')->get('effects');
// $effects is an array keyed by ieids as shown above in the parent reference example.
// Use the getValue() TypedData method to retrieve the value.
$first_ieid = key($effects->getValue());
// Take the data keys for this first effect.
$data = $effects[$first_ieid]['data'];
// Examine values and types for width.
$data['width']->getType(); // will return 'integer'
$data['width']->getValue(); // will return 220
?>

You can even use typed configuration to make changes to config, however this is not suggested as a default practice (typed data is not needed to change configuration). If you already have typed data access for your configuration however, there is no need to change configuration differently.

<?php
$new_slogan
= 'Great new site slogan';
$typed_site_info = \Drupal::service('config.typed')->get('system.site');
$typed_site_info->set('slogan', $new_slogan);
?>

Once again, if you don't need typed config for other reasons, just use Drupal::config(), it is quicker and simpler.

See more code examples around navigating configuration based on the schema as well as form generation based on the schema at http://drupal.org/project/config_inspector

Debugging your schema #

The configuration inspector module provides a user interface to compare schemas with data and see how form generation and translation (when available) would work with the schema when applied to the data. That can be used to find issues in the schema, see http://drupal.org/node/1910624#comment-7088154 for tips on how to use that to debug schemas.

The core Configuration translation module builds an actual user interface on top of the schemas and lets people translate configuration. You can use this module to debug if your configuration is properly translatable and if the translations appear at the right places (on the front end) and not appear at some places (like the back end where people can edit your original configuration).

Even more background information #

Check out #1866610: Introduce Kwalify-inspired schema format for configuration and #1648930: Introduce configuration schema and use for translation for hundreds on top of hundreds of comments where different approaches and solution possibilities were discussed (and even more side issues spawned) before we came to this format. (As well as #1914366: Move all configuration schema files into a schema subdirectory for why they are located where they are). See also #1905152: Integrate config schema with locale, so shipped configuration is translated for information on how the schema system integrates with locale module. #1952394: Add configuration translation user interface module in core is where the translation module was added.

#1602106: Document default configuration files is a start at documenting the regular configuration yml conventions.

Looking for support? Visit the Drupal.org forums, or join #drupal-support in IRC.