How to Write Multiline Strings in YAML — All 6 Methods Explained

Multiline strings in YAML seem simple until one breaks a production deployment at 2 AM.

I have seen a Helm chart fail because a developer used | when they needed > and the trailing newline broke an ingress annotation. I have watched GitHub Actions workflows silently truncate shell commands because the wrong chomping indicator was used.

YAML offers six distinct ways to write multiline strings. Each behaves differently. Pick the wrong one and you get subtle bugs — extra newlines, missing spaces, or completely different values than you expected.

Here is every method, how it actually behaves, and exactly when to use each one.


Block Scalars: Literal and Folded

Block scalars are the most common approach for multiline strings in YAML.

They come in two flavors: literal and folded.

Literal Block Scalar (|)

The pipe character preserves line breaks exactly as written.

multiline: |
  first line
  second line
  third line

Parses as:

first line\nsecond line\nthird line\n

Notice the trailing newline at the end.

Docker Compose uses this pattern constantly:

version: "3.8"
services:
  app:
    build:
      dockerfile: Dockerfile
    command: |
      sh -c "
      npm install &&
      npm run migrate &&
      npm start
      "

Each line break becomes an actual newline in the parsed value. This is ideal for shell scripts, SQL queries, and prose where line boundaries matter.

Folded Block Scalar (>)

The greater-than character replaces single newlines with spaces.

description: >
  This is a long description
  that will be folded into
  a single line.

Parses as:

This is a long description that will be folded into a single line.\n

Hard line breaks in the source file become soft line breaks (spaces) in the value.

Where this matters: Kubernetes annotations.

metadata:
  annotations:
    nginx.ingress.kubernetes.io/configuration-snippet: >
      more_set_headers "X-Frame-Options: DENY";
      more_set_headers "X-Content-Type-Options: nosniff";

YAML folds the lines, but the semicolons in the NGINX snippet remain intact. A literal scalar here would introduce unwanted newlines and break the configuration.


Chomping Indicators: Strip and Keep

Both | and > support optional chomping indicators that control trailing newlines.

This is where most bugs live.

Default Chomping (Clip)

No modifier = one trailing newline is preserved.

default: |
  text

Parses as "text\n".

Strip Chomping (|- or >-)

Removes the final newline entirely.

strip: |-
  text

Parses as "text".

This matters in CI/CD pipelines where trailing whitespace silently breaks string comparisons.

Keep Chomping (|+ or >+)

Preserves all trailing newlines including blank lines at the end.

keep: |+
  text

Parses as "text\n\n".

Real-world example where keep chomping matters:

prompts:
  system: |+
    You are a helpful assistant.

    Follow these rules:
    - Be concise
    - Be accurate

If you used | instead of |+, the blank line before "Follow these rules" would be erased. For prompt engineering in LLM workflows, every empty line changes tokenization.


Quoted Multiline Strings

Sometimes you need to embed characters that YAML interprets as syntax — colons, hashes, brackets, or leading whitespace. That is where quoted multiline strings come in.

Double-Quoted Strings

Double quotes interpret escape sequences.

escaped: "line one\nline two\ttabbed"

Supports standard escape sequences:

  • \n — newline
  • \t — tab
  • \\ — backslash
  • \" — double quote
  • \x41 — Unicode by hex code

Important: YAML double-quoted strings also break long lines. Use continuation lines with indentation:

docker_run: "
  docker run \
    --rm \
    -p 8080:80 \
    nginx
"

This is ugly but necessary when you cannot use block scalars (for example, inside GitHub Actions ${{ }} expressions).

Single-Quoted Strings

Single quotes preserve literal content with no escape processing besides '' for a literal single quote.

warning: 'Don''t use tabs in YAML'

Single quotes are useful when your string contains colons or hashes without triggering YAML parsing:

command: 'echo "version: ${VERSION}"'

The single quotes prevent the colon from being interpreted as a mapping separator.


Plain Scalars (Unquoted Strings)

Unquoted strings are the default in YAML but cannot span multiple lines without help.

key: value

Plain scalars can span lines — YAML calls this "flow" style — but the behavior is unintuitive:

description: this is a
  plain scalar that
  continues

YAML replaces the surrounding newlines with spaces. This is similar to folded style but with no block indentation.

Do not rely on this for multiline content. The rules are subtle and most parsers handle edge cases differently. Use block scalars or quoted strings explicitly.


Real-World Mistakes with Multiline Strings

Mistake 1: Literal instead of Folded in Annotations

# Broken — newlines break the annotation
annotations:
  nginx.ingress.kubernetes.io/rewrite-target: |
    /$2

/$2\n is not the same as /$2. Kubernetes rejects the annotation.

Mistake 2: Trailing Newline in Secrets

apiVersion: v1
kind: Secret
data:
  password: |
    c29tZXBhc3N3b3Jk

Base64-decoded passwords with trailing newlines silently mismatch expected values. Use |- to strip the trailing newline.

Mistake 3: Using Block Scalars Inside GitHub Actions run

- name: Build
  run: |
    npm install
    npm run build

This works because GitHub Actions joins each line. But adding accidental indentation changes behavior:

- name: Build
  run: |
    npm install
      npm run build

The extra spaces become part of the shell command. The build breaks.


Multiline String Decision Guide

NeedUse
Preserve every line break`
Fold lines into single line>
No trailing newline`
Escape sequences neededDouble-quoted "..."
Literal colons/hashesSingle-quoted '...'
Short inline valuesPlain (unquoted)

If you are unsure, start with | (literal block scalar) and add chomping after you verify behavior with your specific parser.


Testing Multiline Strings

Before trusting any multiline method in production, verify the actual output:

import yaml

config = """
key: |
  hello
  world
"""

parsed = yaml.safe_load(config)
print(repr(parsed["key"]))
# 'hello\nworld\n'

The repr() output reveals invisible characters — trailing newlines, embedded tabs, unintended spaces. Run this test for every multiline pattern you use.

Many developers paste YAML into a YAML formatter to see how their multiline strings actually parse before committing. This catches chomping mistakes before they reach CI.

For a deeper look at common YAML parsing issues that multiline strings can trigger, see Why Your YAML Is Invalid and How to Fix YAML Indentation Errors. If you work with Kubernetes, Kubernetes YAML Mistakes covers more real-world multiline failures in deployment manifests.


FAQ

What is the difference between literal and folded block scalars in YAML?

Literal block scalars (|) preserve every line break exactly as written in the source file. Each new line in your YAML becomes a newline character in the parsed value. Folded block scalars (>) replace single newlines with spaces, effectively wrapping long text into paragraphs. Use literal scalars when line structure matters — shell scripts, SQL queries, or multi-line commands. Use folded scalars when you want readable formatting in the YAML file but need the final value as a single continuous line — like Kubernetes annotations, descriptions, or commit messages. Both support the same chomping indicators for controlling trailing newlines.

What does |- mean in YAML?

The |- is a literal block scalar with strip chomping. The pipe indicates literal mode (preserve line breaks) and the minus sign strips the trailing newline. For example, key: |-\ntext\n more\n parses as "text\nmore" without a trailing newline. The strip chomping indicator is essential when the trailing newline would cause bugs — passwords in Kubernetes secrets, checksums, or any scenario where exact string content matters. Without the minus, YAML appends a trailing newline that can silently break string comparisons.

How do I include a newline in a YAML string without using block scalars?

Use double-quoted strings with the \n escape sequence: key: "first line\nsecond line". Double-quoted strings also support \t for tabs, \\ for backslashes, and \xNN for arbitrary bytes. Single-quoted strings do not interpret escape sequences — they preserve the literal characters including backslashes. If you need programmatic newlines inside inline strings (like GitHub Actions expressions or templated values), double-quoted strings with \n are the safest approach because they work within flow mappings and avoid YAML's indentation-sensitive parsing.

Why does my multiline YAML string have extra newlines?

You are likely using the default chomping behavior (clip) which preserves one trailing newline. If your source has multiple blank lines at the end, clip mode keeps only one. If you see no trailing newline but expected one, you might have strip chomping (|- or >-) active. If you see more trailing newlines than expected, you might have keep chomping (|+ or >+). The fix is to explicitly choose your chomping indicator based on whether you want zero, one, or all trailing newlines. Use repr() in Python or JSON.stringify() in JavaScript to inspect the actual parsed value.

Can I use multiline strings in GitHub Actions YAML?

Yes, and you should use literal block scalars (|) for multi-step run commands. GitHub Actions interprets each line break as the shell does, so |\n npm install\n npm run build\n runs two sequential commands. Be careful with indentation inside the block — extra spaces become part of the shell command. For single-line commands that are too long for readability, use folded scalars (>), but test carefully because GitHub Actions handles whitespace differently than standard YAML parsers in edge cases.


Final Thoughts

Multiline strings are one of YAML's most useful features and one of its most common sources of subtle bugs.

The key insight is simple: every method produces a different string value. Literal vs folded, clip vs strip vs keep — these choices change the actual data your application receives.

Test each multiline pattern with a YAML parser before relying on it. The two minutes of verification will save you the hour-long debugging session later.

And when you are stuck trying to figure out why a multi-line value broke your config, paste it into a YAML formatter and validator to see exactly what the parser sees. Most multiline bugs become obvious the moment you inspect the parsed output.