← Back to Blog

YAML Multiline Strings: Pipe vs Fold vs Block (with Examples)

YAML has six ways to write a string. Two of them - the literal block style (|) and the folded block style (>) - are specifically designed for multiline content. They are common in Kubernetes manifests, Docker Compose files, and CI/CD pipelines, yet frequently misunderstood. This guide explains every variant with real examples.

Why YAML Has Multiple String Styles

Unlike JSON, which has exactly one string syntax (double-quoted with escape sequences), YAML offers multiple scalar styles to handle different real-world content types cleanly. The problem that block scalars solve is concrete: how do you embed a shell script, a SQL query, a certificate, or a long prose description inside a YAML config file without a wall of \n escape sequences or ugly quoting?

Consider embedding a shell script in a Kubernetes ConfigMap. The naive approach:

data:
  script.sh: "#!/bin/bash\nset -e\necho 'Starting...'\nnpm install\nnpm run build\necho 'Done'"

That is unreadable and unmaintainable. With a literal block scalar:

data:
  script.sh: |
    #!/bin/bash
    set -e
    echo 'Starting...'
    npm install
    npm run build
    echo 'Done'

The second version is readable, editable, and the newlines are preserved exactly. This is the primary use case for block scalars.

The Six String Styles in YAML

Before diving deep into block scalars, it helps to understand the full landscape of YAML string styles:

  • Plain (unquoted): value: hello world - no quotes, no special processing. Cannot contain special characters like :, #, or leading -.
  • Single-quoted: value: 'it''s a string' - no escape sequences except '' for a literal single quote. Newlines in the source become spaces.
  • Double-quoted: value: "line1\nline2" - full escape sequences (\n, \t, \uXXXX). Most explicit but least readable for multiline.
  • Literal block (|): Preserves newlines exactly as written. Used for scripts, code, certificates.
  • Folded block (>): Folds single newlines into spaces (like HTML). Used for long prose paragraphs.
  • Block scalar with chomping modifiers: Controls the trailing newline behavior. Applied to both | and >.

The block styles are the two that confuse most developers. Let us go through them precisely.

Literal Block Scalar: | (Pipe)

The literal block scalar preserves every newline in the content. Each line in the YAML source becomes a line in the resulting string. This is the right choice when the content is code, configuration, or anything where line breaks are meaningful.

script: |
  #!/bin/bash
  apt-get update
  apt-get install -y curl
  curl -fsSL https://example.com/install.sh | sh

The parsed value of script is:

"#!/bin/bash\napt-get update\napt-get install -y curl\ncurl -fsSL https://example.com/install.sh | sh\n"

Note the trailing newline. By default, YAML appends exactly one newline after the last line of a literal block. This is called the clip chomping behavior and is the default for both | and >.

The indentation rule

The content of a block scalar is determined by indentation. The first non-empty line sets the indentation level. All subsequent lines must be indented at least as much. Leading spaces beyond that indentation level are preserved in the value. This means you can embed content with its own indentation:

nginx_config: |
  server {
      listen 80;
      location / {
          proxy_pass http://backend;
      }
  }

The parsed value includes the inner indentation of the Nginx config block, because those spaces go beyond the YAML indentation baseline.

Folded Block Scalar: > (Greater-than)

The folded block scalar converts single newlines into spaces, effectively joining lines into a paragraph. This is useful for long strings that you want to wrap in the source file for readability, but that should be treated as a single long string at runtime.

description: >
  This is a very long description that wraps
  across multiple lines in the source file
  but will be treated as a single line
  when parsed by the YAML parser.

  A blank line creates a real paragraph break.
  This sentence starts a new paragraph.

The parsed value is:

"This is a very long description that wraps across multiple lines in the source file but will be treated as a single line when parsed by the YAML parser.\nA blank line creates a real paragraph break. This sentence starts a new paragraph.\n"

The blank line between the two paragraphs produces a single newline in the output (not a space). Lines within a paragraph are joined with spaces. The final trailing newline is added by default (clip chomping).

The key difference: | preserves all newlines. > converts single newlines to spaces, but blank lines remain as newlines.

Chomping Indicators: - and +

Both | and > support optional chomping indicators that control how trailing newlines are handled. The indicator is appended directly after the block indicator character:

SyntaxNameTrailing newlines behavior
| or >Clip (default)Exactly one trailing newline
|- or >-StripNo trailing newlines at all
|+ or >+KeepAll trailing newlines preserved

Strip chomping: |-

value: |-
  line one
  line two
  line three

Parsed result: "line one\nline two\nline three" - no trailing newline. Use this when you are embedding a value that should not have a trailing newline, such as a command argument or a database query.

Keep chomping: |+

value: |+
  line one
  line two

  

Parsed result: "line one\nline two\n\n" - the blank line and any trailing empty lines are preserved. Use this when trailing newlines are semantically significant in your content (rare in practice).

The default (clip): |

value: |
  line one
  line two

Parsed result: "line one\nline two\n" - exactly one trailing newline regardless of how many blank lines follow the content. This is the most common behavior and is what you want in almost all cases.

Indentation Indicators

You can explicitly specify the indentation level with a digit after the block indicator. This is needed when the first line of content starts with spaces that you want to preserve, or when the content begins with an empty line:

value: |2
    this content is indented by 4 spaces
    but the YAML indentation level is 2
    so 2 spaces are stripped, 2 are preserved

The 2 in |2 tells the parser that the block's indentation level is 2. Any indentation beyond those 2 spaces is preserved in the value. You can combine chomping and indentation indicators: |2- means "indentation level 2, strip trailing newline."

In practice, explicit indentation indicators are rarely needed. The parser automatically detects the indentation level from the first non-empty content line. You only need them when the first content line is blank or when you need to preserve leading spaces.

Real Examples in DevOps Configs

Kubernetes ConfigMap with embedded script

apiVersion: v1
kind: ConfigMap
metadata:
  name: app-scripts
data:
  entrypoint.sh: |
    #!/bin/sh
    set -e
    echo "Running migrations..."
    python manage.py migrate --no-input
    echo "Starting server..."
    exec gunicorn myapp.wsgi:application \
      --bind 0.0.0.0:8000 \
      --workers 4
  nginx.conf: |
    server {
      listen 80;
      server_name _;
      location / {
        proxy_pass http://127.0.0.1:8000;
        proxy_set_header Host $host;
      }
    }

GitHub Actions multi-line run step

jobs:
  deploy:
    runs-on: ubuntu-latest
    steps:
      - name: Deploy to production
        run: |
          echo "Deploying commit $GITHUB_SHA"
          kubectl set image deployment/app \
            app=myrepo/myapp:$GITHUB_SHA
          kubectl rollout status deployment/app
          kubectl rollout history deployment/app

Docker Compose with healthcheck command

services:
  postgres:
    image: postgres:16
    healthcheck:
      test:
        - CMD-SHELL
        - |
          pg_isready -U postgres &&
          psql -U postgres -c "SELECT 1" > /dev/null
      interval: 10s
      timeout: 5s
      retries: 5

Long description with folded block

metadata:
  annotations:
    description: >
      This deployment runs the payment processing service
      responsible for handling Stripe webhooks and updating
      order status. It requires the STRIPE_SECRET_KEY and
      DATABASE_URL environment variables to be set via the
      payment-secrets Kubernetes Secret.
    runbook: >-
      https://wiki.example.com/runbooks/payment-service

The >- on the runbook URL strips the trailing newline, which is important here because it is a URL that will be used programmatically and should not have a trailing newline character.

Validate Your YAML Block Scalars

Paste your YAML with multiline strings into our free validator. See exactly what value each block scalar parses to, catch indentation errors, and verify your chomping indicators. 100% client side.

Open YAML Validator

Common Mistakes and How to Fix Them

Block scalars are easy to get wrong. Here are the mistakes developers make most often:

Mistake 1: Wrong indentation level

# WRONG: content at same level as key
script: |
#!/bin/bash    # parse error - not indented

# CORRECT: content indented at least one level
script: |
  #!/bin/bash
  echo "hello"

The content of a block scalar must be indented more than the key it belongs to. A common rule of thumb: indent two spaces past the key's column.

Mistake 2: Forgetting the trailing newline difference

# |  gives you: "value\n"   (one trailing newline)
# |- gives you: "value"     (no trailing newline)
# If you pass this to a command and get an extra blank line,
# switch from | to |-

Mistake 3: Using folded block for code

# WRONG: using > for a shell script
script: >
  #!/bin/bash
  apt-get update
  apt-get install -y curl
# Parsed as: "#!/bin/bash apt-get update apt-get install -y curl\n"
# All lines joined with spaces! Script will fail.

# CORRECT: use | for code
script: |
  #!/bin/bash
  apt-get update
  apt-get install -y curl

Mistake 4: Special characters in plain scalars

# WRONG: colon in plain scalar causes parse error
message: Error: connection refused

# CORRECT: quote it or use block scalar
message: "Error: connection refused"
# OR
message: |
  Error: connection refused

Mistake 5: Tabs in block scalar content

YAML does not allow tab characters for indentation, even inside block scalars. If your content includes tabs (common in Makefiles or heredocs), you need to represent them differently or use a different approach. Always use spaces for YAML indentation.

Quick Reference: Choosing the Right Style

  • Shell scripts, Python code, SQL queries, certificates: Use | (literal). Newlines are significant.
  • Multi-line prose descriptions, annotations, commit messages: Use > (folded). You want word-wrap in the source but a single paragraph at runtime.
  • Command arguments, URLs, single-line values that just need quoting: Use double-quoted strings with explicit \n if needed. Or use |- if you genuinely need multiline without trailing newline.
  • Simple strings with no special characters: Use plain (unquoted) scalars. No need for block style.
  • Trailing newline matters to the consumer: Use |- (strip) for no newline, | (clip) for exactly one, |+ (keep) for all.

Frequently Asked Questions

Why does my shell script inside YAML fail with "command not found"?

The most common cause is using > (folded) instead of | (literal). With >, every newline becomes a space, so your multi-line script becomes one long joined string, which the shell cannot parse as separate commands. Switch to | and the newlines will be preserved, making each command its own line.

What is the difference between | and a quoted string with \n?

Functionally they can produce the same string value, but block scalars are far more readable and maintainable for anything longer than one or two lines. With a quoted string, you must manually escape every special character and write explicit \n sequences. With |, you write the content naturally and the parser handles the rest. There is no performance difference - it is purely a readability concern.

Can I use YAML block scalars in all YAML parsers?

Yes. Block scalars are part of the core YAML 1.1 and YAML 1.2 specifications and are supported by every conformant YAML parser. Python's PyYAML, Go's yaml.v3, Ruby's Psych, JavaScript's js-yaml, Java's SnakeYAML - all support them fully. If a tool rejects your block scalar, the issue is likely an indentation error, not parser incompatibility.

Does Kubernetes support YAML block scalars in manifests?

Yes. Kubernetes' API server and kubectl parse YAML fully and support all block scalar styles. Block scalars are commonly used in ConfigMaps (for script content), Job/Pod specs (for multi-line command or args), and annotations (for runbook URLs and long descriptions).

How do I include a blank line inside a literal block without triggering keep-chomping?

Blank lines inside the content of a block scalar are always preserved regardless of the chomping indicator. Chomping only controls what happens to trailing blank lines after the last non-empty line. A blank line in the middle of your content will always appear as an empty line in the parsed string when using |.

Can I use block scalars for YAML keys, not just values?

The YAML specification technically allows complex keys (including block scalars used as keys) with the ? explicit key indicator. In practice, this is never done and no real-world tool supports it usefully. Block scalars are a values-only feature in every practical application.

The Bottom Line

YAML's block scalar syntax is one of its greatest strengths for configuration authoring. Once you internalize the two core rules - | keeps newlines, > folds them - and understand that - strips the trailing newline, you have everything you need for 95% of real-world cases.

For DevOps work specifically: use | for any embedded script or code (Kubernetes entrypoints, GitHub Actions run steps, Ansible shell tasks), use > for long description fields, and use |- when passing values to tools that are sensitive to trailing newlines.

Use our free tool here → YAML Validator to paste any YAML block scalar and instantly see the parsed string value, confirm indentation is correct, and catch any syntax errors before they hit production.

UK
Written by Usman Khan
DevOps Engineer | MSc Cybersecurity | CEH | AWS Solutions Architect

Usman has 10+ years of experience securing enterprise infrastructure, managing high-traffic servers, and building zero-knowledge security tools. Read more about the author.