YAML Pitfalls: Indentation, Implicit Types and Multi-line Strings

By the CodeBeautify team at Softaware Commerce Ltd · Published

Most YAML bugs come from three places: indentation (tabs, or a line indented by one space too many or too few), implicit typing (unquoted values such as no, 1.10 or 0644 turning into booleans or numbers you did not intend) and multi-line strings whose trailing newlines are not what you expected. The safe habits are to indent with spaces only, quote any value that must stay a string, and choose the block scalar indicator deliberately. Every example below was run through js-yaml 4, the parser used by the YAML Formatter and YAML to JSON tools, and the results shown are its real output.

YAML 1.1 versus YAML 1.2: why parsers disagree

Many YAML surprises are not bugs in your file but differences between versions of the specification. YAML 1.1 (2005) recognised a long list of implicit types: yes/no/on/off as booleans, numbers with a leading zero as octal and colon-separated numbers as base 60. YAML 1.2 trimmed this down, and its core schema only treats true/false as booleans. js-yaml 4 follows YAML 1.2, while some widely used libraries, such as Python's PyYAML, still follow 1.1. The same file can therefore produce different data in different tools, so it pays to write YAML that means the same thing under both.

Indentation: tabs and inconsistent levels

YAML uses indentation to express nesting, and the spec forbids tab characters for indentation. A tab at the start of a line is an error:

server:
→port: 8080        # → is a tab

js-yaml reports tab characters must not be used in indentation (2:1). Tabs are allowed inside values (a: x→y parses to a string containing a tab), which is why the problem can hide in a file for a long time. Configure your editor to insert spaces in YAML files.

The number of spaces per level is up to you, but every key at the same level must line up exactly. One extra space is enough to break a mapping:

database:
  host: db.local
   port: 5432

js-yaml stops with bad indentation of a mapping entry (3:8). If the stray line is indented less than its siblings but more than the parent, you get the same error. A line indented back to the parent's level is not an error at all: it silently becomes a key of the parent, which is harder to spot. Sequences under a key may be indented or flush with it; both of these give {"items": ["a", "b"]}:

items:
- a
- b

items:
  - a
  - b

The YAML Formatter shows the parser's message, which gives the position as (line:column) and includes a short excerpt of the lines with a caret under the problem. Clicking Beautify on a file that parses re-indents it consistently with 2 or 4 spaces.

Implicit typing: what an unquoted value becomes

An unquoted (plain) scalar is matched against the schema's patterns, and the first match decides its type. The table compares js-yaml 4 with PyYAML 6 as an example of a YAML 1.1 parser; both columns were produced by running the values through each library.

Plain valuejs-yaml 4 (YAML 1.2)PyYAML 6 (YAML 1.1)If you meant a string
no, offstring "no", "off"false"no"
yes, onstringtrue"yes"
True, TRUEtruetrue"True"
0644644 (decimal)420 (octal)"0644"
0o1412 (octal)string"0o14"
1e31000string "1e3""1e3"
1.101.11.1"1.10"
12:30string "12:30"750 (base 60)"12:30"
~, null, emptynullnull"~"

A few of these deserve a closer look:

  • The "Norway problem". A list of country codes containing NO becomes false under YAML 1.1. js-yaml 4 keeps it as the string "NO", but a 1.1 tool reading the same file will not, so quote it.
  • Version numbers. python: 3.10 is the float 3.1 in both versions. Quote versions: python: "3.10".
  • File modes. mode: 0644 means 420 to a YAML 1.1 parser and 644 to js-yaml. Write "0644" if the consumer expects a string, or 0o644 if both sides speak YAML 1.2.
  • Case matters. js-yaml accepts true, True and TRUE, but tRue is a string. The same applies to null, Null and NULL.
  • Dates. js-yaml's default schema also recognises timestamps, so released: 2026-10-03 is loaded as a date, and the YAML to JSON tool outputs "2026-10-03T00:00:00.000Z". Quote dates you want to keep as written.
  • Special floats. .inf, -.inf and .nan load as Infinity and NaN, which JSON cannot represent; converting to JSON turns them into null. See common JSON syntax errors for why.

You can force a type with a tag: !!str 1.10 loads as "1.10". Quoting is usually clearer. When the YAML Formatter writes YAML (Beautify, or JSON → YAML), js-yaml quotes strings that another parser could misread, so a string no is written as 'no' and 12:30 as '12:30'.

Quoting rules

YAML has three scalar styles on a single line:

  • Plain (unquoted): subject to implicit typing. It cannot start with indicator characters such as @, %, *, &, ! or {, and cannot contain ": " (colon space) or " #" (space hash).
  • Single-quoted: no escapes at all. Backslash is literal, and a single quote is written by doubling it: 'it''s' gives it's.
  • Double-quoted: supports escapes such as \n, \t, \" and \x41.

The colon and hash rules cause confusing errors. msg: a: b fails with bad indentation of a mapping entry, even though the indentation is fine; the second : starts another mapping. a: foo #bar gives "foo", because #bar is a comment, while foo#bar without the space is kept whole. A URL such as http://x.com:8080/p is fine unquoted, because none of its colons is followed by a space. Also check that every key has a space after its colon: key:value is a single string, not a mapping.

Multi-line strings: block scalars and chomping

Block scalars start with | (literal: keep line breaks) or > (folded: join lines with spaces). An optional chomping indicator controls the final newline: none (clip) keeps exactly one, - (strip) removes all, + (keep) preserves every trailing blank line.

HeaderContent linesjs-yaml result
|l1, l2"l1\nl2\n"
|-l1, l2"l1\nl2"
|+l1, l2, blank line"l1\nl2\n\n"
>l1, l2"l1 l2\n"
>-l1, l2"l1 l2"

Folded scalars keep a blank line as a line break and do not fold lines that are indented more than the rest:

note: >
  l1
  l2

  l3
    indented
  l4

This loads as "l1 l2\nl3\n indented\nl4\n". Use | for shell scripts, certificates and anything where line breaks matter, >- for long prose that should become one line, and |- when a trailing newline would break a comparison or a header value.

Anchors, aliases and merge keys

An anchor (&name) labels a node and an alias (*name) reuses it. js-yaml 4 also supports the << merge key, which copies the keys of one or more mappings into another:

base: &base
  x: 1
  y: 2
other:
  <<: *base
  y: 3

Here other loads as {"x": 1, "y": 3}. Keys written in the mapping itself win over merged keys, even if << comes after them, and <<: [*a, *b] merges several mappings. Merge keys were defined as a YAML 1.1 type and are not part of the 1.2 core schema, so not every parser supports them. Converting to JSON expands every alias into a full copy, as JSON has no references. An alias to an anchor that does not exist fails with unidentified alias, which is also what happens if a plain value starts with *.

Multiple documents

--- starts a new document, which is common in Kubernetes manifests. js-yaml's load(), used by both tools on this site, accepts only one document and fails with expected a single document in the stream, but found more; loadAll() returns an array of documents. A single leading --- or a trailing ... is fine. To convert a multi-document file here, paste one document at a time.

Duplicate keys

YAML requires keys in a mapping to be unique, and js-yaml 4 enforces it: a: 1 followed by a: 2 throws duplicated mapping key (2:1). This is stricter than JSON, where duplicates are allowed by the grammar, so a JSON file with repeated keys can parse as JSON yet fail when loaded as YAML. js-yaml's json: true option relaxes this and keeps the last value, but the tools on this site use the default.

Quick reference

  • Indent with spaces only, and align sibling keys exactly.
  • Quote anything that must stay a string: "no", "on", "1.10", "0644", "12:30", dates, postcodes and IDs with leading zeros.
  • Quote values containing ": " or " #", or starting with @ % * & ! { [.
  • Pick | or > deliberately, and add - when you do not want a trailing newline.
  • Check whether every consumer of the file follows YAML 1.1 or 1.2 and supports merge keys.
  • Keep keys unique, and split multi-document streams before loading with a single-document API.

Frequently asked questions

Why did my YAML value "no" stay a string?

js-yaml 4 follows the YAML 1.2 core schema, where only true and false (in lower, title or upper case) are booleans. Parsers that follow YAML 1.1, such as PyYAML, read no as false. Quote the value or write false so every parser agrees.

Is JSON valid YAML?

Mostly: YAML 1.2 was designed so that JSON documents are valid YAML, and js-yaml loads flow syntax such as {"a": [1, 2]}. One difference is duplicate keys, which js-yaml rejects even though JSON's grammar allows them.

How do I write a long string without a trailing newline?

Use a block scalar with the strip indicator: |- keeps your line breaks, >- folds lines into one. Both drop the final newline.

Can I use tabs anywhere in YAML?

Not for indentation. A tab inside a value, such as between two words, is kept as part of the string, but a tab at the start of an indented line is an error.

Tools for this guide