YAML File Standards

Sooner or later, every Drupal project means writing YAML — module info files, routes, services, libraries, configuration. YAML looks forgiving, but its indentation rules are not, and a single misplaced space is enough to make Drupal fail to parse a file at all.

1. Indentation

YAML uses 2 spaces per indentation level, never tabs — a tab character in a YAML file is a parse error waiting to happen, and Drupal's error message for it rarely points at the actual line.

# ❌ Incorrect: no indentation, the nested structure can't be parsed.
product_import.settings:
path: '/admin/config/product-import'
defaults:
_title: 'Product Import'

# ✅ Correct: 2-space indentation shows the nested structure.
product_import.settings:
path: '/admin/config/product-import'
defaults:
_title: 'Product Import'

2. Common YAML Files

A handful of YAML file types show up in nearly every module: .info.yml declares the module itself, .routing.yml defines its routes, .services.yml its services, .libraries.yml its CSS/JS assets, and .permissions.yml its permissions. Each one follows the same 2-space indentation rule, and each has its own well-documented top-level structure.

name: 'Product Import'
type: module
description: 'Imports products from the external catalog API.'
package: Custom
core_version_requirement: ^10 || ^11
dependencies:
- drupal:node

3. Routing Definitions

A route name is prefixed with the module's machine name, same as everything else. _controller points to the class and method that handles the request; _title sets the page title; requirements controls who's allowed to reach it.

product_import.status_page:
path: '/admin/reports/product-import'
defaults:
_controller: '\Drupal\product_import\Controller\ImportController::status'
_title: 'Import status'
requirements:
_permission: 'administer product import'

4. Service Definitions

Service YAML files follow the pattern covered in the architecture chapter: a namespaced service ID, the class it instantiates, and its constructor arguments as other service IDs prefixed with @. Booleans in a service definition — like shared: false — are lowercase, unlike the TRUE/FALSE used in PHP.

services:
product_import.importer:
class: Drupal\product_import\ProductImporter
arguments: ['@logger.factory', '@http_client']

5. Config Schema

Any module that ships configuration — a settings form backed by \Drupal::configFactory(), for instance — needs a matching schema file under config/schema/, describing the type of every key the configuration can contain. Skipping this doesn't break anything obviously at first, but it silently breaks configuration translation, validation, and the config import/export workflow.

product_import.settings:
type: config_object
label: 'Product Import settings'
mapping:
api_endpoint:
type: string
label: 'API endpoint URL'
batch_size:
type: integer
label: 'Batch size'

6. Quoting and Booleans

A plain unquoted string is fine for most values, but anything that could otherwise be parsed as a number, a boolean, or that starts with a special character gets wrapped in single quotes to keep YAML from misreading it. Double quotes are reserved for strings that need escape sequences.

# ❌ Incorrect: unquoted values that YAML will misinterpret.
version: 2.0
enabled: yes
path: /admin/config

# ✅ Correct: quoted where it actually matters.
version: '2.0'
enabled: true
path: '/admin/config'

Latest update: 21.09.2026 09:14