YAML Cheatsheet

Scalars and Types

Use this YAML reference while you build software engineering projects, review code, or refresh the syntax you reach for most.

There are no type declarations

A plain (unquoted) scalar gets its type from a regex resolver, and that resolver changed between YAML 1.1 and YAML 1.2. This reference targets 1.2 core and flags 1.1 wherever they disagree, because most parsers in the wild are a mix: PyYAML's safe_load is effectively 1.1, Go's yaml.v3 and ruamel.yaml default to 1.2, Ruby Psych sits in between.

count: 3          # int
ratio: 0.5        # float
name: api         # str
ready: true       # bool
extra:            # null

The resolution table

Every row below was parsed, not remembered. "1.1" is PyYAML 6.0 safe_load, "1.2" is the 1.2 core schema.

Plain scalarYAML 1.2 coreYAML 1.1 (PyYAML)
null · Null · NULL · ~ · emptynullnull
true · True · TRUEbool truebool true
yes · on · No · OFFstringbool
1 · +12 · -17intint
1_00010001000
0x1F3131
0b10155
0o1715string "0o17"
0171715 (octal)
088string "08"
1.5 · -0.01floatfloat
1e31000.0string "1e3"
1.0e31000.0string "1.0e3"
6.02e+23floatfloat
.inf · .Inf · -.INF±infinity±infinity
.nan · .NaNNaNNaN
12:30string750 (sexagesimal)
1.101.11.1
1.0.0stringstring
2024-01-15date (see below)date

Two rows there cause most real incidents. 017 means fifteen to a 1.1 parser and seventeen to a 1.2 parser. 0644 becomes 420, which is exactly why file modes in Kubernetes and Ansible have to be quoted.

Null and empty

Four spellings mean null, and a missing value means null too. An empty quoted string is a string, not null.

a:              # null
b: null         # null
c: ~            # null
d: Null         # null
e: ""           # empty string, NOT null
f: "null"       # the four-character string "null"

Parsed: {a: None, b: None, c: None, d: None, e: '', f: 'null'}. If your code does if value: both null and "" are falsy, so the distinction only bites when you check is None or serialize back out.

Integers

FormExampleValue
decimal4242
signed+42 · -4242 · -42
underscore separated1_000_0001000000
hex0x1F · 0xDEADBEEF31 · 3735928559
octal (1.2)0o755493 in 1.2, string in PyYAML
octal (1.1)0755493 in 1.1, 755 in 1.2
binary0b1101_0110214

Underscores are permitted in 1.1 and, as a widely implemented extension, in 1.2 parsers too. Do not rely on them for a config another team's parser reads.

Floats and the special values

plain: 3.1415
good_exp: 6.626e-34      # float everywhere
also_good: 1.0e+3        # float everywhere
bad_exp: 1e3             # float in 1.2, STRING in PyYAML
also_bad: 5e+22          # float in 1.2, STRING in PyYAML
still_bad: 1.0e3         # float in 1.2, STRING in PyYAML
positive_inf: .inf
negative_inf: -.Inf
not_a_number: .nan

PyYAML's float regex demands both a decimal point and an explicitly signed exponent, so only the 1.0e+3 shape is portable. 1e3, 5e+22, and 1.0e3 all come back as strings from safe_load while a 1.2 parser reads them as floats. There is no Infinity or NaN spelling in YAML, only the dot forms, and the letters are case insensitive (.inf, .Inf, .INF).

Timestamps

YAML 1.1 defines a timestamp type. The 1.2 core schema does not, so a strict 1.2 parser leaves a date as a string while most real implementations (PyYAML, ruamel, yaml.v3) still resolve it.

date: 2024-01-15                  # date
utc: 2024-01-15T10:30:00Z         # datetime, UTC
offset: 2024-01-15T10:30:00-07:00 # datetime with offset
spaced: 2024-01-15 10:30:00       # datetime, naive
version: "2024-01"                # quoted, stays a string

Because the type is parser dependent, quote any date you want as text. A 2024-01-15 that arrives as a Python datetime.date instead of a str is a common source of "not JSON serializable" crashes downstream.

Strings are the fallback

Anything the resolver does not recognise is a string, which is why most YAML looks untyped and works anyway. The failure mode is the reverse: a value you meant as text that happens to match a number, bool, or date pattern.

# all of these need quotes
mode: "0644"        # else 420 in 1.1
release: "1.10"     # else the float 1.1
country: "NO"       # else false in 1.1
duration: "12:30"   # else 750 in 1.1
tag: "3.9"          # a float otherwise, and 3.10 sorts wrong

Rule of thumb: quote every version, mode, ID, country code, and time. It costs two characters and removes a whole class of bug.