YAML Cheatsheet
Comments and Documents
Use this YAML reference while you build software engineering projects, review code, or refresh the syntax you reach for most.
Document markers
| Marker | Meaning |
|---|---|
--- | start of a document |
... | end of a document (optional) |
%YAML 1.2 | version directive, must precede --- |
%TAG | tag shorthand directive |
A single-document file needs no --- at all. A leading --- is conventional and
harmless, and it becomes mandatory once you have directives or more than one
document.
%YAML 1.2
---
name: api
...The %YAML directive is how you ask for 1.2 semantics explicitly. Support varies:
ruamel.yaml honours it, PyYAML parses it but still resolves types with its 1.1
rules, so do not rely on it to fix the Norway problem.
Multi-document streams
One file, many documents, separated by ---. This is how kubectl apply -f takes
several manifests in one file.
--- apiVersion: v1 kind: Service metadata: name: api --- apiVersion: apps/v1 kind: Deployment metadata: name: api
Loading a stream needs the plural API. The singular one is an error, not a silent first-document read.
| Language | Single | Stream |
|---|---|---|
| Python | yaml.safe_load(f) | yaml.safe_load_all(f) |
Go (yaml.v3) | yaml.Unmarshal | yaml.NewDecoder(r).Decode in a loop |
| Ruby | YAML.load | YAML.load_stream |
Node (yaml) | YAML.parse | YAML.parseAllDocuments |
| CLI | yq '.' | yq eval-all / yq '.' file prints all |
import yaml with open("manifests.yaml") as f: for doc in yaml.safe_load_all(f): print(doc["kind"], doc["metadata"]["name"])
yaml.safe_load on that same file raises
ComposerError: expected a single document in the stream. If you have ever seen
that message, you have a --- you did not account for.
Empty documents and stray separators
--- a: 1 --- --- b: 2
That is three documents, and the second is null. A trailing --- at the end of
a file adds an empty document too, so a: 1 followed by --- loads as
[{'a': 1}, None]. Templating engines produce these constantly, so
filter them:
docs = [d for d in yaml.safe_load_all(text) if d]
kubectl and helm both tolerate null documents. Your own loader probably does
not, and the failure shows up as 'NoneType' object is not subscriptable.
Directives and %TAG
%TAG defines a shorthand prefix, which is how tools shorten long tag URIs. You
will read these more often than you write them.
%TAG !e! tag:example.com,2024: --- thing: !e!widget size: 3
!e!widget expands to tag:example.com,2024:widget. Directives apply to the
next document only, so a multi-document stream repeats them after each ---.
Practical notes
- The --- must be at column zero with nothing before it on the line. An indented
--- inside a block is just three dashes of content.
- ... is rarely used. Its one real job is ending a document in a stream that
continues with non-YAML content, and closing a document before a directive.
- A BOM is allowed at the very start of a stream and is ignored there. A BOM
anywhere else becomes part of a scalar. a: 1\nb: 2 parses with the key
"b", which looks identical to b in every editor.
- A file that begins --- | is a single document whose whole body is one literal
string, which is occasionally what you want for embedding a script.
Comments
#is the only comment syntax. There is no block comment form, so a commented region needs a#on every line.The rules that matter:
- A
#starts a comment only at the start of a line or after whitespace.tag: a#bgives the stringa#b, the hash is inert. - A#inside a quoted scalar is literal.a: '# not a comment'is the string# not a comment. - A#inside a block scalar is literal too, which is how shell scripts and Dockerfiles survive a|block. - Comments are not part of the data model. A round trip throughyaml.safe_loadthenyaml.dumploses every comment. Useruamel.yamlwithtyp="rt"if you need to edit a file and keep them.