TL;DR — YAML syntax in one minute
- Use spaces only (no tabs). 2-space indent is the de-facto convention.
key: valueneeds a space after the colon.key:valueis a single string.- Lists use
-(dash + space). Mappings usekey: value. - Quote values that could be misread as numbers, booleans, or dates:
version: "1.0". - Catch errors fast by pasting into FormatArc's YAML to JSON validator — line-numbered messages point straight to the problem.
What is YAML and where you will see it
YAML (YAML Ain't Markup Language) is a human-readable text format for structured data. It covers the same data model as JSON — strings, numbers, booleans, nulls, lists, and mappings — but the syntax is based on indentation instead of braces and brackets.
If you work with modern DevOps or cloud-native tools, you have already met YAML:
- Kubernetes manifests and Helm charts
- Docker Compose files
- GitHub Actions, GitLab CI, and CircleCI workflows
- Ansible playbooks
- OpenAPI specifications
- Static site generators (Hugo, Jekyll, Eleventy)
This guide walks through every part of the YAML syntax you will actually use, with examples and a list of the mistakes that catch beginners most often. If you want to compare YAML to JSON directly, see YAML vs JSON: Key Differences, or read the step-by-step How to Convert YAML to JSON.
The three building blocks
Everything in a YAML file is one of three things:
- Scalar — a single value such as a string, number, boolean, or null
- Sequence — an ordered list of items
- Mapping — an unordered set of key-value pairs
You can nest these freely. A mapping can contain lists, a list can contain mappings, and both can contain other mappings or lists. That is all you need to represent any JSON-compatible structure.
Indentation rules
Indentation is how YAML expresses nesting. A few rules to internalize:
- Use spaces, never tabs — a literal tab character is a syntax error in most parsers
- Pick one indent size (2 spaces is the convention) and stay consistent
- All items at the same level must have the same indentation
- Indentation determines structure; there is no closing delimiter
Here is a simple valid example:
database:
host: localhost
port: 5432
credentials:
user: admin
password: secret
database contains a mapping, which contains host, port, and credentials. credentials is a nested mapping. The two-space indent tells YAML exactly how the structure fits together.
Writing key-value pairs (mappings)
A mapping is written as key: value, with a space after the colon. The space is required — key:value is a single string, not a mapping.
name: Alice
age: 30
is_admin: true
bio: null
Keys are usually plain strings. They can contain spaces if quoted:
"full name": Alice Cooper
Values can be any YAML type: scalar, sequence, or another mapping.
Writing lists (sequences)
A sequence is a series of lines starting with - (a dash followed by a space).
fruits:
- apple
- banana
- cherry
Lists can hold any type of value, including nested mappings:
users:
- name: Alice
role: admin
- name: Bob
role: editor
Each dash starts a new list item. The fields that follow belong to that item until another dash appears at the same indentation level.
An inline (flow) form also exists, which looks like JSON:
fruits: [apple, banana, cherry]
users: [{name: Alice, role: admin}, {name: Bob, role: editor}]
The flow form is handy for small lists but is harder to read once data grows. Stick with the block form for anything non-trivial.
Strings: when to quote
Most strings in YAML can be written without quotes.
greeting: Hello, world
You only need quotes when:
- The value would otherwise be parsed as a different type:
version: "1.0",country: "NO",postal_code: "07030" - The value starts with a special character such as
-,:,[,{,|,>,*,&,!,%,@ - The value contains a colon followed by a space (which would otherwise look like a mapping)
- You want to include escape sequences such as
\nor\t
YAML supports both single and double quotes. Double quotes interpret escape sequences; single quotes treat everything literally.
escaped: "line1\nline2"
literal: 'line1\nline2'
Multi-line strings
YAML has two operators for long text blocks.
The literal block (|) preserves newlines exactly as written:
description: |
This is line one.
This is line two.
This is line four, after a blank line.
The folded scalar (>) replaces single newlines with spaces, which is useful for wrapping long paragraphs in the source file:
paragraph: >
This long sentence is split across
multiple lines in the source, but YAML
will fold it into a single line.
Chomp indicators: strip, clip, and keep trailing newlines
YAML's block scalars accept a chomping indicator that decides what happens to the trailing newlines after the value. The indicator goes immediately after | or >.
| Indicator | Behavior | Example |
|---|---|---|
| (none) — clip | Keep a single final newline. Default. | | |
- — strip |
Remove every trailing newline. | |- >- |
+ — keep |
Keep every trailing blank line. | |+ >+ |
clip: |
line one
line two
strip: |-
line one
line two
keep: |+
line one
line two
The same three modes apply to the folded form (>, >-, >+). Use |- when you embed YAML inside a JSON or environment variable that must not have a trailing newline. Use |+ when you generate scripts where the trailing blank lines matter.
Types and parser inference
YAML infers types from unquoted values. The same literal can become an integer, a float, a date, or a string depending on which YAML version your parser implements and which schema it uses.
integer: 42
hex: 0xFF # 255 (integer, hex)
octal: 0o17 # 15 (integer, octal, YAML 1.2)
octal_legacy: 0644 # 420 (integer, octal, YAML 1.1) — file mode trap
float: 3.14
negative: -7
exponential: 1e3 # 1000.0 (float, scientific notation)
infinity: .inf # +Infinity (float)
negative_infinity: -.inf
not_a_number: .nan # NaN (float)
boolean_true: true
boolean_false: false
null_value: null
tilde_null: ~
date: 2026-04-13
timestamp: 2026-04-13T09:30:00Z
Any of these can be forced to a string by quoting:
version_string: "1.0" # not the number 1.0
zip_code: "07030" # not the number 7030
file_mode: "0644" # string, not octal integer
The next table summarises what unquoted literals become in a modern YAML 1.2 Core schema loader (PyYAML, js-yaml, SnakeYAML's default), versus a YAML 1.1 loader (legacy PyYAML, Symfony YAML). Most surprises live in this column gap.
| Literal | YAML 1.2 Core | YAML 1.1 | Watch out for |
|---|---|---|---|
42 |
integer (42) | integer (42) | — |
0xFF |
integer (255) | integer (255) | hex parse is intentional |
0o17 |
integer (15) | (unrecognized) | YAML 1.2 only |
0644 |
integer (644) | integer (420, octal) | file modes change between versions |
1e3 |
float (1000.0) | float (1000.0) | display drops .0 in some tools |
.inf / -.inf |
float (±Infinity) | float (±Infinity) | not JSON-serializable |
.nan |
float (NaN) | float (NaN) | not JSON-serializable |
2026-04-13 |
string | date (date object) | round-tripping JSON ↔ YAML changes type |
2026-04-13T09:30Z |
string | timestamp | same issue |
true / false |
boolean | boolean | — |
yes / no / on / off |
string | boolean | the "Norway problem" |
NO |
string | boolean (false) | covered next |
~ / null / Null / NULL |
null | null | every casing parses |
1.0 |
float (1.0) | float (1.0) | re-serialized as 1 by some tools |
If your parser is anywhere near production, treat anything in the right-most column as a potential bug. The fastest way to find out which version your loader uses is to paste a representative file into FormatArc's YAML to JSON converter and inspect the JSON output: "NO" versus false, "2026-04-13" versus an ISO date string, and so on.
The Norway problem: when NO becomes false
The "Norway problem" is the most-quoted YAML gotcha. YAML 1.1 parsers (still the default in many tools — including PyYAML, Symfony YAML, and SnakeYAML's older releases) interpret a bare NO as the boolean false. So a country code list written this way:
countries:
- DE
- FR
- NO
- SE
silently becomes ["DE", "FR", false, "SE"] once parsed. The same trap fires for YES, ON, OFF, Y, N, and even capitalised variants depending on the parser.
The fix is to quote any value that could be misread:
countries:
- "DE"
- "FR"
- "NO"
- "SE"
YAML 1.2 narrowed booleans to just true and false, but most production tools still ship a 1.1-era loader. When you write country codes, two-letter language codes, version strings, or any short identifier, quote it. If you maintain a parser-agnostic config, paste it into FormatArc's YAML to JSON converter — the JSON output instantly shows whether your NO survived as a string.
The exact rules live in the official specs: the YAML 1.2.2 spec defines today's strict boolean set, while the YAML 1.1 spec is the one most legacy tools still implement. Knowing which version your loader follows tells you whether NO parses as a string or as false.
Comments
Comments start with # and run to the end of the line. They can occupy a line on their own or follow a value.
# Maximum number of retries for upstream requests
retries: 3 # Any higher and we exceed the upstream timeout
Comments are one of YAML's biggest wins over JSON for configuration files — use them to explain why a setting exists, not what it is. Converting to JSON will drop every comment, so keep the YAML file as your source of truth. If you have to live with JSON, see How to Add Comments to JSON for the JSONC and JSON5 workarounds.
Anchors and aliases for reuse
YAML lets you define a value once with &anchor and reference it elsewhere with *alias. The <<: merge key pulls a mapping in as a set of defaults.
defaults: &defaults
adapter: postgres
host: db.internal
pool: 5
development:
<<: *defaults
database: myapp_dev
production:
<<: *defaults
database: myapp_prod
pool: 20
production starts from defaults and then overrides pool. This is a clean way to share configuration between environments without copy-pasting.
Merge keys (<<:) for sharing config
The <<: merge key is anchors' most useful companion. It pulls the keys from one mapping into another, so you can express "same as that, but with these overrides" without repeating yourself. Docker Compose, GitLab CI, and Rails database.yml all rely on this pattern.
base: &base
image: node:20
restart: unless-stopped
environment:
NODE_ENV: production
services:
api:
<<: *base
command: npm run start:api
worker:
<<: *base
command: npm run start:worker
environment:
NODE_ENV: production
WORKER_QUEUE: high
Two things to remember:
- The merge key only merges at one level. Nested mappings (like
environmentabove) are replaced wholesale, not deep-merged. - The merge key is a YAML 1.1 feature. Strict YAML 1.2 parsers may ignore it; tools like Docker Compose and GitLab still support it because they pin to 1.1 semantics.
Schemas and tags: how YAML decides types
YAML 1.2 defines three schemas that control how unquoted literals are interpreted:
- FailSafe — only strings, mappings, and sequences. The safest schema; everything else is a string. Rarely the default.
- JSON — JSON-compatible types: string, integer, float, boolean, null, mapping, sequence. Matches what
JSON.parsewould produce. - Core — the typical default. Adds the type inference rules in the table above (hex, octal,
.inf,.nan,~).
Most parsers ship Core (PyYAML's safe_load, js-yaml's default, SnakeYAML's SafeConstructor). Symfony YAML still defaults to YAML 1.1 semantics. Knowing which schema your loader uses tells you which row in the type table applies.
When the schema gets it wrong — version: 1.0 parsing as a float when you wanted a string — use an explicit tag to override:
version: !!str 1.0 # forces string "1.0"
count: !!int "42" # forces integer 42 even though it is quoted
empty: !!null "" # explicit null instead of empty string
The !! prefix means "use the tag from the default tag library." Custom tags use a single ! (for example !Ref in CloudFormation templates) and require the parser to understand them. Most application-level YAML files only need !!str to disarm the type traps.
Multi-document files
A single YAML file can contain several documents separated by ---.
---
kind: Service
name: web
---
kind: Deployment
name: web
replicas: 3
This is the same format Kubernetes uses when you concatenate several manifests. JSON has no equivalent — converting a multi-document YAML to JSON forces you to either pick one document or wrap them in an outer array.
Real-world YAML examples
The same syntax rules apply whether you are writing a five-line config or a production manifest. Here are the three most common places you will encounter YAML in 2026.
Kubernetes Deployment manifest
A minimal Deployment exercises mappings, sequences, multi-line strings, and the Norway-problem booleans all at once. Most failures here come from indentation drift in the spec.template.spec.containers block.
apiVersion: apps/v1
kind: Deployment
metadata:
name: web
labels:
app: web
tier: frontend
spec:
replicas: 3
selector:
matchLabels:
app: web
template:
metadata:
labels:
app: web
spec:
containers:
- name: web
image: nginx:1.27-alpine
ports:
- containerPort: 80
env:
- name: FEATURE_FLAG_NEW_HEADER
value: "true" # quoted on purpose — bare true is a boolean
resources:
limits:
cpu: "500m"
memory: 256Mi
Notice value: "true" is quoted. Without the quotes Kubernetes would happily accept the boolean and your container would receive True (Python style) or fail to start depending on the language. The mistake-by-mistake breakdown is in the next section.
GitHub Actions workflow
GitHub Actions workflows are deeply nested mappings with frequent multi-line scripts. The run block is where YAML's | (literal) and > (folded) modifiers earn their keep.
name: CI
on:
push:
branches: [main]
pull_request:
branches: [main]
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: "20" # quoted to keep the trailing zero
- name: Install
run: npm ci
- name: Test
run: |
npm run lint
npm run build
npm test -- --run
The most common breakage here is removing the quotes around node-version: "20" — YAML coerces it to the integer 20, which setup-node may or may not accept depending on the version. Quote any value that "looks like" a number but should stay a string.
Docker Compose service
Compose files mix mappings, sequences, environment dictionaries, and bind-mount strings. They are short enough to paste in full and are a great way to practice spotting indentation issues.
services:
web:
image: nginx:1.27-alpine
ports:
- "8080:80" # quoted to avoid sexagesimal parsing
environment:
NGINX_HOST: example.com
NGINX_PORT: "80" # quoted: env values must be strings
volumes:
- ./html:/usr/share/nginx/html:ro
depends_on:
- api
api:
build: ./api
restart: unless-stopped
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost/health"]
interval: 30s
timeout: 5s
retries: 3
Two subtle traps here: "8080:80" must be quoted (otherwise YAML 1.1 parsers can read it as a sexagesimal number), and every value under environment: should be quoted because Docker Compose ultimately needs strings. The mistakes that follow show what these look like when they go wrong.
Common mistakes and how to catch them
These are the YAML mistakes that trip up almost everyone at some point.
- Tabs instead of spaces — a hidden tab character breaks parsing with a confusing error
- Missing space after the colon —
key:valueis one string, not a mapping - Inconsistent indentation — items at the same level must share the same indent
- Booleans from unquoted
NO,OFF,YES,ON— the "Norway problem" - Colons in unquoted values —
time: 10:30might parse as a sexagesimal number - Mixing block and flow syntax in ways the parser does not expect
- Duplicate keys in the same mapping — behavior is implementation-defined
The fastest way to catch these is to let a parser read the file. You can do that without installing anything by pasting your YAML into FormatArc's YAML to JSON converter. If the file is valid, you will see the JSON equivalent immediately. If it is not, the error message includes the line number so you can jump to the problem.
Validate your YAML in the browser with FormatArc
FormatArc's browser-only tools work well as a quick YAML linter:


- YAML to JSON — paste YAML, see JSON output, and get line-numbered errors when something is wrong
- JSON to YAML — starting from JSON and want to see the YAML equivalent? Use this to learn the mapping between the two formats
- JSON Formatter — pretty-print the JSON that comes out of your YAML
Everything runs in the browser. Nothing leaves your machine, so you can safely paste internal config files or secrets while you are debugging.
Frequently asked questions
Can I use tabs for indentation in YAML?
No. The YAML spec requires spaces, and a literal tab character will produce a syntax error in most parsers. Configure your editor to insert two spaces when you press Tab in a .yml or .yaml file.
What is the difference between .yml and .yaml?
None. Both extensions are treated identically by every YAML parser. The official spec recommends .yaml, but .yml is common and fully supported.
Do I always need quotes around strings?
No. YAML lets you write most strings without quotes. Add quotes only when the value would otherwise be parsed as a different type, or when it starts with a special character such as -, :, [, {, |, or >.
Why does version: 1.0 become 1?
YAML parses 1.0 as a floating-point number, and some tools later serialize the float as 1. To keep it as a string, quote it: version: "1.0".
How many spaces should I use for indentation?
Two is the convention used by Kubernetes, Docker Compose, GitHub Actions, and most examples you will see online. Four is fine as long as you are consistent inside a single file.
How do I validate a YAML file without installing anything?
Paste it into FormatArc's YAML to JSON converter. If the YAML is valid, you will see the JSON output immediately. If it is not, the error message includes the line number so you can fix the problem fast.
Can I write multiple YAML documents in one file?
Yes. Separate them with a line containing just ---. This is how Kubernetes manifests are typically bundled. A single YAML file can hold as many documents as you want.
Related reading
- What is YAML? — the high-level introduction if you are new to the format
- YAML vs JSON: Key Differences — compare YAML and JSON side-by-side
- How to Convert YAML to JSON — step-by-step conversion guide
- JSON Syntax Guide — the JSON equivalent of this article
- How to Add Comments to JSON — the workaround when you cannot use YAML
Summary
- YAML uses indentation, not braces, to express structure
- Spaces only — never tabs, and be consistent
- Quote strings only when they would be misinterpreted
- Use comments, multi-line strings, and anchors to make config files readable
- Validate your YAML fast by pasting it into FormatArc's YAML to JSON tool