DevKitLab Logo DevKitLab
YAML / Strings / Parsing / Configuration

YAML Multiline Strings: When to Use | and When to Use >

A multiline value in YAML has two block symbols, a pipe and a greater-than sign, plus three scalar styles — plain, single-quoted, and double-quoted — that also span lines. Each keeps or folds your line breaks differently, and picking wrong turns a script into one broken line. Here is the whole picture, with the corners that bite.

You want a multiline value in YAML. Maybe a shell script, a long description, a block of SQL. YAML gives you two symbols for it: | and >. Pick the wrong one and nothing errors. The value is just quietly wrong. A script under > collapses to one line and stops running. A description under | keeps every break you added only to fit your screen.

The two are usually called “literal” and “folded.” True, but useless in the moment you have to choose. Here is the question that actually decides it: do the line breaks in this text mean something?

If they do, keep them. Use |. The newlines in a script separate commands. The newlines in a table separate rows. If they don’t — you wrapped one long sentence so it would fit the editor — let them go. Use >.

That answers most cases. But block scalars have more corners than that, and a few of them bite. This post walks the whole thing: the two symbols, the trailing newline, the indentation trap, and the fact that block scalars are not even the only way to write a multiline string.

| keeps breaks; > folds them to spaces

Same input, two symbols, two different strings. With |:

message: |
  line one
  line two

you get "line one\nline two\n". The break stays. With >:

message: >
  line one
  line two

you get "line one line two\n". The break became a single space, as if you had typed one long line.

That space is the whole point of >. It is for prose. You wrap a long description in the file so it reads well, but it should arrive as one line. It is exactly wrong where the breaks are structure. Watch it wreck a script:

script: >
  set -e
  echo hi
  echo bye

That folds to "set -e echo hi echo bye\n". Three commands, one line. The same block under | gives "set -e\necho hi\necho bye\n", which is what you meant. When in doubt, use |. Keeping breaks you don’t need is often harmless; losing breaks you need is a bug.

What > doesn’t fold: extra indentation and blank lines

Folding has an exception, and it is worth knowing. A line indented more than the rest of the block is not folded. It keeps its own breaks, literally. Watch:

note: >
  prose that wraps
  onto two lines
    indented line, kept as-is
  back to prose

That gives "prose that wraps onto two lines\n indented line, kept as-is\nback to prose\n". The wrapped prose folded to spaces. The indented line kept its newlines and its indentation. So a folded block can hold a literal island — you can wrap a paragraph and still embed a code sample inside it.

It is also a trap. An accidental extra space turns one line literal. Your output grows a newline you never wanted, and nothing warns you.

Blank lines are the other thing > leaves alone. A blank line inside a folded block does not collapse. It becomes a newline. So > still separates paragraphs, even while it folds the breaks inside each one:

text: >
  first paragraph

  second paragraph

That gives "first paragraph\nsecond paragraph\n". A break within a paragraph would fold to a space. The blank line between paragraphs stays a newline.

Chomping: how many trailing newlines

Once you have picked | or >, one small thing is left at the end of the block. What happens to the newline after the last line. YAML calls this “chomping.” It has three modes.

  • Default, “clip”: exactly one trailing newline, however many blank lines you leave. | gives "text\n".
  • Strip, -: no trailing newline. |- gives "text".
  • Keep, +: every trailing newline you wrote. |+ on a block with two blank lines after it gives "text\n\n\n".

Most of the time the default is fine and you never think about it. It matters in two cases. When a trailing newline breaks something — a value you hash, a token, a filename — use -, as in key: |-. When trailing blank lines carry meaning and must survive, use +. Otherwise leave it alone.

When the text itself starts with spaces

Here is a subtle one. YAML decides a block’s indentation from its first non-empty line. Usually that is what you want. But sometimes your content starts with spaces that are real — an already-indented snippet, say. YAML reads those leading spaces as the block’s own indentation and strips them. Worse, if a later line is indented less, the block ends early and you get a parse error.

The fix is the indentation indicator: a digit right after | or >. It states the indent instead of guessing.

code: |2
      leading spaces kept
    less-indented line

The 2 means two spaces of indentation relative to the parent, not an absolute column. Here the key sits at column zero, so content begins at column two. Nest the same key one level deeper and |2 would mean two spaces past that level instead. So leading spaces kept keeps four spaces, and less-indented line keeps two. You can combine the digit with chomping in either order: |2- and |-2 mean the same thing.

Block scalars are not the only way

You can write a multiline string with no symbol at all. Plain, single-quoted, and double-quoted scalars all span lines too. All three fold the same way: an ordinary break between two lines becomes a space, but a blank line still becomes a newline. (The rule that keeps more-indented lines is special to > — it does not apply to these.)

plain:  first line
  second line
single: 'first line
  second line'
double: "first line
  second line"

All three give "first line second line". The main differences are escapes, quoting, and the syntax restrictions of plain scalars.

Double quotes understand escapes. "a\nb" is two lines. "tab\there" has a real tab. A backslash at end of line joins the next line with no space. Single quotes understand none of that. 'a\nb' is a literal backslash, then n. To put one quote inside single quotes, double it: 'it''s here' is it's here. Block scalars never interpret escapes at all — a \n inside | or > is a backslash and an n.

Here is the whole set, side by side:

StyleLine breaks\n escapesTrailing newline
| literalkeptnoone, tunable with - / +
> foldedfolded to spaces (blank line = newline; more-indented lines kept)noone, tunable with - / +
"double"breaks fold to spaces; blank line = newlineyesnone
'single'breaks fold to spaces; blank line = newlineno ('' = one quote)none
plainbreaks fold to spaces; blank line = newlinenonone

Two things fall out of this table. If you want real newlines, only | gives them without escapes. If you want escapes like \n or \t, only double quotes read them.

Every line ending becomes \n

One more normalization, easy to forget. YAML rewrites every line break in a scalar to \n. A file saved on Windows with CRLF endings still parses to \n, not \r\n. You cannot smuggle a carriage return through the source. If you genuinely need one, escape it in a double-quoted string: "line\r\n".

Where this bites in real life

The same rule keeps showing up. Whenever the value is itself a file or a script, it wants |.

  • A Kubernetes ConfigMap embeds a whole config file as a value. That value is almost always |. The newlines are the file.
  • GitHub Actions writes shell steps as run: |. Each line is a command, so the breaks have to stay.
  • Docker Compose and Ansible do the same for inline scripts and file contents.

> shows up for the opposite job: human text. A long description: field. A comment you wrapped for width. If the value is prose, fold it. If it is a file, keep it.

The decision, in short

  • Do the breaks mean something? Yes → |. No, just wrapping → >.
  • Not sure? Use |. Keeping breaks is safe; losing them is a bug.
  • When the breaks are part of the content — code, scripts, tables, embedded files — use |.
  • Need \n or \t escapes? Double quotes. Need literal newlines? |.
  • Trailing newline is set by chomping: default one \n, - none, + all.
  • Content starts with spaces? Use the indentation indicator, like |2.

See the real string

You cannot tell any of this by reading the YAML. A \n and a space look identical on the page. So check. Paste the file into the YAML converter and read the JSON output. Every newline shows as an explicit \n, every fold as a space, every trailing newline as it really is. You see the string your program receives, not the shape of the source.

This is part of the config-format cluster, next to the pillar on choosing between JSON, YAML, and TOML and why does my YAML break on the loud and silent ways YAML fails. Block scalars are one more place where YAML does something reasonable that is not what you assumed. The fix, as always, is to look at the real value instead of trusting the source.