You've now met two kinds of configuration — a simple key/value setting, and a full config entity with several fields. Both of them share one silent dependency you haven't seen yet: neither would actually work correctly in a real Drupal site without a schema file telling Drupal exactly what shape their data takes. This lesson closes out the Configuration topic by explaining what a schema is, why Drupal insists on one, and how to read one line by line.
Why schema exists at all
YAML files, by themselves, are just text — every value technically arrives as a string unless something tells Drupal otherwise. A schema is that "something." It's a YAML-based contract that declares, for every key your module's configuration can contain, its exact data type and a human-readable label. Without one, Drupal can't safely export, import, translate, or validate your configuration, and the built-in Configuration Management UI will start showing warnings about it.
What you'll learn in this lesson
- Where schema files live and how Drupal discovers them automatically
- How a single glob-pattern key can cover every instance of a config entity type
- What each built-in schema type (
string,label,boolean,config_entity) actually controls - Why an untyped boolean is a classic, hard-to-debug source of bugs
The source file
Path (relative to the Examples module's root): modules/config_entity_example/config/schema/config_entity_example.schema.yml
# Schema for the configuration files of the Config Entity Example module.
# This schema tells the config system how to read our config YML files.
# See for example the file config/config_entity_example.robot.marvin.yml, which
# contains our default config entity.
# Documentation for schema files like this one is available on
# https://drupal.org/node/1905070
config_entity_example.robot.*:
type: config_entity
label: 'Robot'
mapping:
id:
type: string
label: 'Robot id'
uuid:
type: string
label: 'UUID'
label:
type: label
label: 'Label'
neural_system:
type: boolean
label: 'Neural system'
langcode:
type: string
label: 'Default language'
How it works
Where the file lives, and how Drupal finds it
Schema files live in a module's config/schema/ directory and must be named <module_name>.schema.yml. That's genuinely the whole registration process — Drupal auto-discovers every file matching this pattern across all installed modules at bootstrap. There's no hook to implement and nothing to declare in your .info.yml.
The top-level key is a glob pattern, not a literal name
config_entity_example.robot.*:
This is the detail that trips people up the first time they read a schema file: that top-level key isn't the name of one config object — it's a pattern that matches many of them. The trailing * means this single block of YAML applies to every config object whose name starts with config_entity_example.robot. — so it covers config_entity_example.robot.marvin, config_entity_example.robot.r2d2, and any robot a site administrator creates later, all from one definition. You write the shape once; it applies to every record of that type forever.
type: config_entity
type: config_entity
This declares the base schema type for the whole block. config_entity is one of several built-in base types Drupal ships with, and choosing it means this schema automatically inherits handling for properties every config entity shares — things like uuid and langcode — without you having to redeclare that behavior yourself. The other base types you'll run into are config_object (for module-level settings, like the one from the first two lessons in this topic), sequence (ordered lists), and plain mapping (a generic key/value structure with no special inheritance).
mapping: — the actual field definitions
Everything under mapping corresponds, key for key, to what actually appears in a stored config YAML file like config/install/config_entity_example.robot.marvin.yml. Let's go through each one, since together they demonstrate the four schema types you'll use constantly.
id and uuid — type: string
id:
type: string
label: 'Robot id'
uuid:
type: string
label: 'UUID'
Both are plain string values — machine identifiers, not display text. id is the robot's own machine name (like marvin); uuid is a universally unique identifier Drupal generates automatically at creation, used to match records across environments during import. Neither should ever be translatable, which is exactly what type: string (rather than type: label) signals.
label — type: label
label:
type: label
label: 'Label'
This is a special type, and it's easy to skim past. type: label behaves like string but additionally marks the value as translatable through Drupal's Interface Translation system — meaning it can be translated using the config_translation module with zero extra code from you. Use label for any short, human-facing name that a site might display in another language; use plain string for identifiers that should never change no matter what language a user has selected.
neural_system — type: boolean
neural_system:
type: boolean
label: 'Neural system'
This is the entry worth paying the closest attention to. Declaring type: boolean tells Drupal to cast this YAML value to a genuine PHP bool. Leave the schema entry out entirely, and Drupal instead hands you the raw string 'true' or 'false' — and in PHP, the string 'false' is truthy. Any strict comparison like if ($robot->neural_system === TRUE) would then silently fail even when the stored value looks correct in the YAML file. This is exactly the kind of bug that's painful to track down later, and exactly what a schema exists to prevent.
langcode — type: string
langcode:
type: string
label: 'Default language'
Stores the language code for the record (for example en). It's typed as a plain string because it's a language tag identifier, not translatable display text — Drupal's multilingual configuration system reads this field when config translation is active.
Why this matters beyond just "avoiding a warning"
During drush config:import (or the equivalent step in the Configuration Synchronization admin UI), Drupal validates every imported YAML file against its registered schema. A missing or incorrect schema doesn't just produce a cosmetic warning — it causes real, functional problems: values that should be translatable stay locked as plain strings, boolean fields get compared incorrectly at runtime exactly as described above, the Status Report page flags the module, and — if your site or CI pipeline runs with strict config schema checking enabled — automated tests will start failing outright.
See it for yourself
Schema itself has no visual admin page of its own — its effects show up indirectly, in how correctly your configuration behaves elsewhere. Visit /examples/config-entity-example on your own DDEV site to see the config entities whose data this exact schema file is validating and typing behind the scenes.
Quick check: if you added a new property called
favorite_colorto theRobotclass and toconfig_export, but forgot to add a matching entry under this schema'smapping, what would happen? If you said "Drupal would treat it as an untyped value and the Configuration Management UI would start warning about it," you've connected this lesson back to the previous one correctly.
Key takeaways
- Schema files go in
config/schema/<module_name>.schema.ymland are auto-discovered — no registration step required. - Use a glob pattern as the top-level key (like
config_entity_example.robot.*) so one schema definition covers every instance of a config entity type. - Declare
type: config_entity(orconfig_objectfor simple settings) to inherit Drupal's built-in handling for shared fields likeuuidandlangcode. - Use
type: label— nottype: string— for any human-readable name that should be translatable through the Interface Translation system. - Typed boolean fields are critical: without
type: boolean, Drupal reads YAML booleans as raw strings, and the string'false'is truthy in PHP. - Every key in your
config/install/*.ymlfiles needs a matching entry in the schema'smapping— unschematized keys produce warnings and silently break config translation.
Coming up next
You've now covered the full arc of Drupal configuration: reading and writing simple settings, building the form that manages them, defining a whole config entity type, and typing it correctly with schema. Next, we move from configuration a site builder sets once into content — full custom content entities that end users create constantly, starting with how to define one from scratch.