TS-30: YAML

YAML is a human-readable data serialization language, widely used for configuration files, CI/CD pipeline definitions, infrastructure-as-code manifests, and other structured data that people are expected to read and edit directly. It is a strict superset of JSON, extended with block indentation, comments, and several human-friendly conveniences that JSON does not offer.

This technical standard covers YAML syntax and authoring conventions: document structure, indentation, scalar quoting, collection style, comments, anchors and aliases, type resolution, schema validation, security, and tooling. For network APIs and their schema, see TS-29: JSON Schema. For YAML as used specifically in GitHub Actions workflows and custom actions, see TS-60: GitHub Actions, which builds on the general conventions set out here.

Overview

YAML ("YAML Ain’t Markup Language") is a human-readable data serialization language. Its design goal is to be easy for a person to read and write, while remaining a fully machine-parsable format for representing scalars, sequences, and mappings — the same data model used by JSON.

YAML is a strict superset of JSON: any valid JSON document is also valid YAML. YAML adds a block-indented syntax, comments, anchors and aliases for referencing repeated data, and multiple scalar styles, on top of what JSON can express.

When to use YAML

YAML is a good choice for:

  • Configuration files read primarily by humans (application config, CI/CD pipelines, infrastructure-as-code manifests).
  • Data that benefits from comments explaining non-obvious values.
  • Documents that mix long-form text (via block scalars) with structured data.

When not to use YAML

Avoid YAML when a document needs:

  • Machine-to-machine data exchange where parsing speed and unambiguous typing matter more than human readability — prefer JSON.
  • A strict, versioned contract validated at both ends — prefer JSON with TS-29: JSON Schema, or protocol buffers.
  • To be generated and consumed exclusively by machines, where YAML’s human-oriented features (comments, anchors, multiple scalar styles) add parsing risk and ambiguity without any corresponding benefit.

GitHub Actions workflows and custom actions — used throughout this organization’s repositories for CI/CD — are defined entirely in YAML. See TS-60: GitHub Actions for workflow-specific guidance that builds on this standard.

Specification and versions

YAML is defined by the YAML Ain’t Markup Language (YAML) Version 1.2.2 specification, maintained by the YAML community. Two major versions are in active use:

  • YAML 1.1 (2005). Defines a looser "core schema" with a wide set of implicit types, including twenty-two different boolean literals (y, n, yes, no, on, off, and their case variants, in addition to true and false).
  • YAML 1.2 (2009, revised as 1.2.2 in 2021). Tightens the core schema to align with JSON: the only implicit booleans are true/True/TRUE and false/False/FALSE; yes, no, on, and off are plain strings. YAML 1.2 is a strict superset of JSON.

Content authors SHOULD write YAML that is unambiguous under both versions — in particular, always quote strings that could be misread as a 1.1 boolean (see Booleans, nulls, and implicit typing) — because the parsing library in use is not always under the author’s control.

Important

Many widely-used YAML libraries still default to YAML 1.1 semantics, regardless of what the document itself targets. PyYAML’s default loader, for example, follows YAML 1.1’s core schema. This is the root cause of the "Norway problem": a country code written as NO is silently parsed as the boolean false by a 1.1-conformant parser. Do not assume a document is safe just because it targets YAML 1.2 in principle — the parser actually reading it is what matters.

A document MAY declare its target version with a %YAML directive immediately before the document start marker (%YAML 1.2), but this is rarely done in practice, since most consuming tools ignore it or fix their version at the library level.

File extensions

.yaml is the officially recommended extension, and has been since 2006. .yml persists from the era of 8.3 (DOS-style) filename limits and is still the dominant convention in some ecosystems (Ruby/Rails, Docker Compose).

New projects SHOULD use .yaml. An existing project SHOULD stay consistent with whichever extension it already uses throughout — do not mix .yaml and .yml within the same project. Where a tool mandates a specific extension (eg. a framework that only auto-discovers config.yml), follow the tool’s requirement.

Document structure

A YAML stream MAY contain more than one document. A document boundary is marked with three hyphens (---); an optional end-of-document marker uses three dots (…​).

---
name: first document
---
name: second document
...

The start marker (---) is REQUIRED when a stream contains more than one document, since it is what separates them. For a single-document file, the start marker is technically optional, but it SHOULD still be included: it disambiguates the start of YAML content from any leading directives or comments, and it signals unambiguously to both tooling and readers that the file is YAML, not some fragment of another format.

The end marker (…​) is RARELY needed outside multi-document streams — it exists mainly to close a document that is not immediately followed by another ---, such as when a document is embedded in a larger non-YAML stream. Omit it in ordinary single-document files.

Multi-document streams are used by tools that need to bundle several independent, self-contained resources into one file — for example, a Kubernetes manifest that defines a Deployment and a Service in the same file. Where a format’s own tooling does not use multi-document streams, prefer one document per file.

Indentation

YAML structure is determined entirely by indentation. Each node MUST be indented further than its parent; sibling nodes MUST share the same indentation level.

Indentation MUST use spaces only. Tab characters are illegal in YAML indentation — the specification forbids them outright, because different systems and editors render tab width inconsistently. A tab where a space is expected produces an immediate parse error in a conformant parser, and this is one of the most common causes of "my YAML won’t parse" — one made worse because the tab character is often invisible in the editor. Configure editors to insert spaces when the tab key is pressed, in any file with a .yaml/.yml extension.

The specification does not mandate a fixed indent width; any consistent number of spaces is valid. Two spaces per level is RECOMMENDED and is the prevailing convention across widely-used YAML-based tools (Kubernetes, GitHub Actions, Docker Compose, Ansible). Whatever width is chosen, it MUST be applied consistently throughout a document.

A block sequence item’s dash (-) MAY sit at the same indentation level as its parent mapping key, since the dash itself counts as part of the indentation:

parent:
- child-one
- child-two

This is valid, but indenting sequence items one level further than their parent key is RECOMMENDED, since it makes the nesting visually unambiguous and matches the convention used by most formatters and by this standard’s own examples:

parent:
  - child-one
  - child-two

Whichever convention is chosen, apply it consistently throughout a project.

Scalars and quoting

A scalar is a single value — a string, number, boolean, or null. YAML offers four scalar styles: plain (unquoted), single-quoted, double-quoted, and the block styles (literal and folded, covered separately below).

Plain versus quoted strings

Plain (unquoted) scalars are RECOMMENDED for ordinary identifiers and short strings, since they are the most readable form:

name: deploy-production
environment: staging

A string MUST be quoted where leaving it unquoted would change its meaning. This includes:

  • A value that YAML’s implicit typing would otherwise resolve to a boolean, null, or number — see Booleans, nulls, and implicit typing below.
  • A value that starts with a character that is structurally significant in YAML: - ? : , [ ] { } # & * ! | > ' " % @.
  • A string that is empty, or that consists only of whitespace.
  • A string that contains a colon followed by a space (: `), or a hash preceded by a space ( #`), since both are parsed as syntax rather than content wherever they appear inside a plain scalar.

Single-quoted strings support only one escape: two adjacent single quotes ('') represent one literal single quote. No backslash escapes are processed, which makes single quotes a good default for quoted strings that do not need escape sequences.

Double-quoted strings support the full set of C-style escape sequences (\n, \t, \uXXXX, etc.) and are REQUIRED where a string needs one of those escapes. Prefer single quotes unless an escape sequence is actually needed.

Booleans, nulls, and implicit typing

YAML resolves certain unquoted scalars to booleans, nulls, or numbers rather than strings. Under the YAML 1.2 core schema, only true/True/TRUE and false/False/FALSE are booleans, and null is null/Null/NULL/~/ (an empty value). Under YAML 1.1’s looser core schema, yes, no, on, off, y, n, and their case variants are also booleans — twenty-two literals in total.

Because many widely-deployed YAML libraries still default to YAML 1.1 semantics (see Specification and versions), a plain scalar such as a country code (NO for Norway), a feature flag string (on), or a survey answer (Y) can be silently misparsed as a boolean. This is widely known as the "Norway problem." Quote any string value that coincides with a YAML 1.1 boolean literal, regardless of which version the document nominally targets:

country_code: "NO"
feature_flag: "on"

The same hazard applies to numbers. A value with a leading zero (0123) is parsed as an octal literal by a YAML 1.1 parser (0o123 is the equivalent YAML 1.2 octal notation). A version string such as 1.10 is parsed as the float 1.1 if left unquoted — the trailing zero, which distinguishes version 1.10 from version 1.1, is lost, because floating-point values do not preserve trailing zeros. Quote any numeric-looking string that is semantically a string, not a number — version numbers, zip/postal codes, and identifiers with leading zeros:

version: "1.10"
postal_code: "07030"

Block scalars

A literal block scalar (|) preserves line breaks exactly as written. A folded block scalar (>) folds line breaks into spaces, except around blank lines and more-indented lines, which are preserved. Both are suited to multi-line text such as shell scripts, commit message templates, or descriptive prose embedded in a document:

script: |
  #!/bin/sh
  echo "Each line break is preserved."
  echo "Line two."

description: >
  This text will be folded into a single line, with line breaks
  converted to spaces, unless a blank line is encountered.

A chomping indicator MAY follow | or > to control trailing newline behavior: - (strip) removes all trailing newlines, + (keep) preserves them all, and the default (clip) keeps a single trailing newline. Prefer the default unless a specific tool requires otherwise.

Block scalars are RECOMMENDED over quoted strings containing literal \n escapes for any text longer than one line — they are dramatically more readable and produce cleaner version-control diffs.

Collections and style

YAML has two collection types — mappings (key-value pairs, equivalent to a JSON object) and sequences (ordered lists, equivalent to a JSON array) — and two presentation styles for both: block style (indentation-based) and flow style (JSON-like, with explicit brackets).

# Block style.
person:
  name: Ada Lovelace
  languages:
    - English
    - French

# Flow style — equivalent to the above.
person: { name: Ada Lovelace, languages: [English, French] }

Block style is RECOMMENDED as the default for hand-authored YAML. It is more readable at a glance, and — because it is a native fit for a line-based version control system such as Git — it produces far cleaner diffs than flow style.

Consider a list of branch names. Written in flow style, adding a second branch changes the one line that already existed:

on:
  push:
    branches: ["main"]

Written in block style, adding a second branch adds a new line and leaves the existing line untouched, so the diff shows only the addition:

on:
  push:
    branches:
      - main
      - develop

Prefer block style for any sequence or mapping that is expected to change over time — most configuration is exactly this kind of data. Flow style MAY be used for short, stable, single-line collections where the compactness aids readability more than the block form would (eg. a two-element coordinate pair, or a small enum-like set of values that rarely changes).

Do not mix block and flow style within the same collection. A collection is either written in block style or flow style, consistently, from its opening key to its closing value.

Comments

A comment begins with a hash () and runs to the end of the line. A hash MUST be preceded by whitespace (or be at the start of a line) to be treated as a comment — a immediately after non-whitespace content is a literal character, not a comment marker, unless the enclosing scalar is double-quoted.

# A full-line comment, explaining the setting below.
retry_count: 3  # An inline comment.

Comments are one of YAML’s genuine advantages over JSON, which has no comment syntax at all. Use comments to document anything that is not self-evident from the key name and the surrounding structure — the reason for an unusual value, a link to the ticket that explains a workaround, or a warning about a setting that looks safe to change but isn’t.

Comments SHOULD NOT restate what a well-named key and value already make obvious. Reserve them for the "why," not the "what."

Comments are stripped by every YAML parser and are not part of the data model. A comment is lost if a YAML document is round-tripped through a parser that re-serializes it (unless that parser specifically preserves comments, as some round-tripping libraries do) — do not rely on a comment surviving programmatic edits to a file.

Anchors, aliases, and merge keys

An anchor (&name) marks a node for reuse; an alias (*name) inserts a copy of that node elsewhere in the same document. This is YAML’s built-in mechanism for avoiding repetition:

defaults: &defaults
  timeout: 30
  retries: 3

service-a:
  <<: *defaults
  timeout: 60

service-b:
  <<: *defaults

The << merge key merges the mapping referenced by the alias into the current mapping, with any keys already present in the current mapping taking precedence — service-a above ends up with retries: 3 (inherited) and timeout: 60 (overridden). The merge key was a YAML 1.1 convention that the 1.2 specification does not formally define, but it remains near-universally supported by parsers, and it is safe to rely on.

Anchors and aliases are scoped to a single document. A document cannot define an anchor and alias it from a different file or a different document in a multi-document stream.

Use anchors and aliases for genuine, low-risk duplication — repeated configuration blocks that are tedious and error-prone to keep in sync by hand. Avoid them for:

  • Security-sensitive values. Permission blocks, secret references, and access-control configuration SHOULD be written out explicitly at each use site, so that a reviewer can see the effective value directly, rather than having to trace an alias back to its anchor.
  • Deep or cross-cutting reuse. The merge key is shallow — where both the aliased mapping and the current mapping define a key whose value is itself a mapping, the current mapping’s value replaces the aliased one entirely; it does not recurse. Deeply nested reuse is a sign that the configuration would be better generated by a templating tool than hand-maintained with anchors.
  • Tools with partial or no support. Some YAML-consuming tools parse YAML with a restricted feature set and do not resolve anchors and aliases as expected, or forbid them outright. Confirm anchor/alias support in the specific tool before relying on them.

See also Security for the denial-of-service risk that unrestricted alias expansion introduces when parsing untrusted YAML.

Types and tags

YAML resolves most scalars to a type implicitly, using the rules of its core schema (see Booleans, nulls, and implicit typing). A tag (! or !!) MAY be used to state a type explicitly, overriding implicit resolution:

port: !!str 8080
enabled: !!bool "true"

The double-exclamation form (!!str, !!int, !!bool, !!float, !!null, !!map, !!seq) refers to YAML’s built-in tag set. A single exclamation (!MyType) refers to an application-defined or local tag, resolved by whatever schema the consuming tool applies.

Explicit tags SHOULD be used sparingly, and only where implicit resolution would otherwise misinterpret a value — as an alternative to quoting in the cases described in Booleans, nulls, and implicit typing. Quoting is usually the simpler and more portable fix; reach for an explicit tag only where quoting does not apply (eg. forcing an unquoted-looking value’s type in a generated file).

Language-specific tags — such as PyYAML’s !!python/object/apply — that instruct the parser to construct arbitrary objects or invoke code during deserialization MUST NOT be used in any document that may originate from, or be edited by, an untrusted source. See Security.

Schema validation

YAML itself has no built-in schema language beyond its core-schema type resolution. Structural validation — required keys, value types, enums, nested shape — SHOULD be delegated to JSON Schema, since YAML’s data model is a superset of JSON’s: a YAML document parses to the same in-memory structure a JSON Schema validator already expects. See TS-29: JSON Schema for how to design a schema.

Editor and IDE tooling (the YAML Language Server, used by VS Code’s YAML extension and other editors) can validate a file against a JSON Schema as the file is edited, either via a modeline comment at the top of the file, or via an editor-level file-to-schema mapping (eg. VS Code’s yaml.schemas setting) that avoids modifying the file itself:

# yaml-language-server: $schema=https://example.com/schemas/config.json

Where a document’s shape matches a well-known, publicly-schema’d format (GitHub Actions workflows, Kubernetes manifests, and many others), the JSON Schema Store provides a ready-made schema, and tooling that consults it can validate the file with no per-project configuration at all.

For CI-time validation, run the same JSON Schema validator used elsewhere in the project’s toolchain against the parsed YAML document, rather than introducing a second, YAML-specific schema library.

Security

YAML parsing untrusted input carries risks that JSON parsing does not, because several YAML libraries default to a permissive mode that goes beyond building a plain data structure.

Arbitrary object construction

Some YAML libraries support language-specific tags that instruct the parser to instantiate arbitrary objects, or even invoke code, while deserializing a document. PyYAML’s default yaml.load() (prior to library defaults changing in newer releases) is the best-known example: a document containing a tag such as !!python/object/apply could execute arbitrary code the moment it was parsed. This class of vulnerability is not unique to Python — any language whose YAML library supports open-ended type tags is exposed.

Any YAML document that may originate from an untrusted source (an uploaded file, a webhook payload, a value pulled from user input) MUST be parsed with a "safe" loader that restricts deserialization to YAML’s built-in scalar, sequence, and mapping types, and refuses custom or language-specific tags (yaml.safe_load() in PyYAML, and the equivalent safe/restricted mode in other libraries). Never use an "unsafe" or "full" loader on untrusted input.

Denial of service via alias expansion

An anchor referenced by nested aliases can expand exponentially when the parser resolves it — the YAML analogue of the XML "billion laughs" attack. A few kilobytes of source YAML, built from anchors that alias other anchors, can expand into gigabytes of in-memory data, exhausting memory or CPU before the application ever inspects the parsed value.

A safe loader mitigates this in the same way it mitigates arbitrary object construction, but SHOULD be paired with an explicit resource limit (maximum document size, maximum nesting depth, or a parser-level alias-expansion limit, where the library offers one) whenever parsing YAML from an untrusted source. Do not rely on a safe loader alone as a complete defense against resource-exhaustion attacks.

General guidance

  • Treat any YAML document from outside the project’s own trust boundary as untrusted input, and validate it against a schema (see Schema validation) before acting on its contents.
  • Prefer a YAML library’s safe/restricted loading mode as the default for all parsing, reserving the full/unsafe mode — if the library even distinguishes one — for cases where the document’s origin is fully trusted and the extra type-construction behavior is actually required.
  • Do not embed secrets directly in YAML files committed to version control. See TS-52: Security and secrets management.

Readability and formatting

YAML files SHOULD be written primarily with readability in mind — that is the entire premise of choosing YAML over a denser format such as JSON.

  • Consider maintenance and diffs. Because Git and most other version control systems are line-based, favor the formatting choices described in Collections and style (block style over flow style) and Scalars and quoting (block scalars over escaped newlines), which keep future changes to isolated, readable lines.
  • Order keys meaningfully. Group related keys together, and put the keys a reader most needs first (eg. name and description before less consequential configuration) rather than relying on alphabetical order, which optimizes for machine sorting rather than human comprehension.
  • Avoid excessive nesting. Deep nesting (more than four or five levels) is hard to scan and easy to indent incorrectly. Where a document grows this deep, consider whether it should be split across multiple files or restructured.
  • Use a soft line-length limit. Keep lines under 80 columns where practical, matching the same convention used for prose in this repository (see TS-26: Technical writing style guide). A long value (a URL, a block scalar) MAY exceed the limit rather than being awkwardly wrapped.
  • Use comments liberally. Document anything not intuitively understood from the key name, the value, and the surrounding structure — see Comments.
  • Keep a document self-consistent. Pick one indent width, one quoting style, one sequence-indentation convention, and apply it throughout the file. Consistency within a document matters more than which specific convention is chosen.

Tooling

A linter SHOULD be used to enforce consistent YAML style across a project. yamllint is RECOMMENDED. It is configurable per-project and checks indentation consistency, line length, trailing whitespace, duplicate keys, and the truthy/boolean ambiguity described in Booleans, nulls, and implicit typing (its truthy rule restricts implicit booleans to true/false by default, catching the Norway problem at lint time).

A formatter such as Prettier or yamlfmt MAY be used to automate indentation and quoting style. Formatting and linting SHOULD be reconciled so they do not produce conflicting results.

Editor/IDE integration via the YAML Language Server SHOULD be used where available, for real-time schema validation and syntax checking (see Schema validation).

A YAML parser’s safe/restricted loading mode SHOULD be the one exercised in CI validation, matching the mode used at runtime (see Security), so that a document which fails to parse safely is caught before merge rather than in production.

Editor configuration SHOULD ensure:

  • Tabs are never inserted for indentation (see Indentation).
  • Trailing whitespace is highlighted.
  • Files end with a single newline character.

References

  • YAML Language Development Team (2021). YAML Ain’t Markup Language (YAML) Version 1.2.2. — The current YAML specification; the primary source for this standard’s terminology and syntax rules.
  • Wikipedia. YAML. — Background on YAML’s history, the .yaml/.yml extension recommendation, and the 1.1/1.2 boolean-resolution difference.
  • yamllint. yamllint documentation. — Reference for the linter recommended in this standard, including its default rule set (line length, indentation, truthy/boolean checks).
  • redhat-developer. YAML Language Server. — The editor/IDE tooling this standard recommends for real-time schema validation.
  • Sourcery. Remote Code Execution via Unsafe YAML Deserialization in PyYAML. — Background on the arbitrary-code-execution risk this standard’s security guidance is written to prevent.
  • Wikipedia. Billion laughs attack. — Background on the exponential-expansion denial-of-service risk posed by unrestricted alias resolution.