Last updated January 20, 2015. Created on February 1, 2013.
Edited by Gábor Hojtsy, pfrenssen, quietone, 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 Ruby 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

Cheat sheet

For a quick understanding and some handy examples, see this cheat sheet, then read on if you still have questions:

An introductory example #

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

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

(Whether maintenance is actually enabled is stored in the state system and not in configuration.)

The default values for this configuration object are stored in the core/modules/system/config/install/system.maintenance.yml file as:

message: '@site is currently under maintenance. We should be back shortly. Thank you for your patience.'
langcode: en

Each module can have as many configuration objects 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. 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? #

  1. 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.
  2. We also use schemas to provide actual translation forms for configuration based on your data. This use case is where types gain more importance and labels become crucial. The core Configuration translation module uses schemas to generate translation forms and save translations. Built-in translatable types are 'label' for one-line text input and 'text' for multiline text input.
  3. Using the knowledge embedded in the configuration schemas about what is stored of a configuration entity, the default persistence implementation for configuration entities requires a configuration schema for the configuration entity, so the right properties are exported with the types defined. Although it is better to provide configuration schemas, if you really don't want, implement the toArray() method in your configuration entity implementation to not require schema for saving configuration entities of your type.
  4. Last but not least configuration schema is also used to automatically typecast values to their expected types. This ensures that although PHP and web forms in general favour strings over all other types, the right types are used when saving configuration. That is important so when deploying configuration, only actual changes will show up in the difference, no random type changes.

See http://drupal.org/project/config_inspector for a module to help with debugging your schemas. The module helps find missing schemas and schema elements with various views on your data and schema.

There are other ideas for schemas that contributed modules might provide, eg. generating web service interfaces based on some of them. There are most likely other use cases people will find that we did not even think about.

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;
  • class: Only to be used on base types to assign the class implementing parsing (see below for examples on TypedData 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. The keys and types of the values in the mapping need to be described in the schema. Only string keys are allowed in mappings.
    • sequence: Property on value of the sequence type, used to list the underlying elements in the sequence. Keys can be either integers or strings, they do not matter.

Types supported in metadata files #

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

# Undefined type used by the system to assign to elements at any level where
# configuration schema is not defined. Using explicitly has the same effect as
# not defining schema, so there is no point in doing that.
undefined:
  label: 'Undefined'
  class: '\Drupal\Core\Config\Schema\Undefined'

# Explicit type to use when no data typing is possible. Instead of using this
# type, we strongly suggest you use configuration structures that can be
# described with other structural elements of schema, and describe your schema
# with those elements.
ignore:
  label: 'Ignore'
  class: '\Drupal\Core\Config\Schema\Ignore'

# Basic scalar data types from typed data.
boolean:
  label: 'Boolean'
  class: '\Drupal\Core\TypedData\Plugin\DataType\Boolean'
email:
  label: 'Email'
  class: '\Drupal\Core\TypedData\Plugin\DataType\Email'
integer:
  label: 'Integer'
  class: '\Drupal\Core\TypedData\Plugin\DataType\Integer'
float:
  label: 'Float'
  class: '\Drupal\Core\TypedData\Plugin\DataType\Float'
string:
  label: 'String'
  class: '\Drupal\Core\TypedData\Plugin\DataType\String'
uri:
  label: 'Uri'
  class: '\Drupal\Core\TypedData\Plugin\DataType\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. The two remaining (more complex) data types defined based on class implementations:

# Container data types for lists with known and unknown keys.
mapping:
  label: 'Mapping'
  class: '\Drupal\Core\Config\Schema\Mapping'
sequence:
  label: 'Sequence'
  class: '\Drupal\Core\Config\Schema\Sequence'

Mapping as shown above is a key-value pair list type ("associative array" or "hash") where each element may have a different type, while Sequence is a simple indexed list ("indexed array") where elements are either of the same type or are based off the same dynamic type name (see below) and the keys are irrelevant.

All the rest of the types defined in configuration schemas (including system.maintenance itself) simply derive from other types, for example "label", "path", "text", "date_format" and "color_hex" are all defined as strings. The distinction of these types could help tools parsing the schema to identify textual types for different purposes.

# 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

# PHP Date format string that is translatable.
date_format:
  type: string
  label: 'PHP date format'
  translatable: true
  translation context: 'PHP date format'

# HTML color value.
color_hex:
  type: string
  label: 'Color'

Note that the label, text and date_format types are also marked as translatable. This means the core interface translation module will identify items with these types and translate based on community or admin provided translations from the database, creating translation override files. Note that translatable strings may get context with the translation context key such as shown here for date formats. This way strings like 'Y' will get an additional 'PHP date format' context, so translators know it is not an abbreviation of 'Yes' but a PHP date format for years.

The same way, you can define reusable complex types on top of base types by using the format explained above for maintenance mode:

# Mail text with subject and body parts.
mail:
  type: mapping
  label: 'Mail'
  mapping:
    subject:
      type: label
      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
label: 'E-mail settings'
mapping:
  cancel_confirm:
    type: mail
    label: 'Account cancellation confirmation'
  password_reset:
    type: mail
    label: 'Password recovery'
  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)'
  register_pending_approval_admin:
    type: mail
    label: 'Admin (user awaiting approval)'
  status_activated:
    type: mail
    label: 'Account activation'
  status_blocked:
    type: mail
    label: 'Account blocked'
  status_canceled:
    type: mail
    label: 'Account cancelled'
  langcode:
    type: string
    label: 'Default language'

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/install/image.style.medium.yml that has this YAML data structure:

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

Here the structure of the data key depends on the type of the effect, which is specified in the id 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: config_entity
  label: 'Image style'
  mapping:
    name:
      type: string
    label:
      type: label
      label: 'Label'
    effects:
      type: sequence
      sequence:
        - type: mapping
          mapping:
            id:
              type: string
            data:
              type: image.effect.[%parent.id]
            weight:
              type: integer
            uuid:
              type: string

This defines metadata for all image styles (image.style.*) as a mapping of name, label, 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. The key of the sequence is the uuid of the effect, but that does not matter, sequences don't care about their keys. Common values for effects are id, data and weight, however the content of the data depends on the parent's id 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. See Configuration file coding standards. 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 uuid 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 uuids as shown above in the parent reference example.
// Use the getValue() TypedData method to retrieve the value.
$first_uuid = 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 and in fact the schema is used even if you use the common configuration save method. 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.

AttachmentSize
ConfigSchemaCheatSheet1.3.pdf63.66 KB

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