DevToolBoxGRÁTIS
Blog

Strings multilinhas YAML: Block Scalar, Folded e Literal explicados

6 min de leituraby DevToolBox

YAML multiline strings are one of the most confusing parts of the YAML specification. With 6 block scalar combinations, 3 chomping indicators, indentation controls, and flow scalars, it is easy to get lost. This guide covers every multiline string style with practical examples from Docker Compose, Kubernetes, GitHub Actions, and Ansible.

1. Why YAML Multiline Strings Are Confusing

YAML offers at least 9 different ways to write a string that spans multiple lines. Unlike JSON (which only has \n inside double quotes) or TOML (which has triple-quoted strings), YAML gives you fine-grained control over how newlines and trailing whitespace are handled. This power comes at the cost of complexity.

The two main block scalar styles are Literal (|) and Folded (>). Each can be combined with three chomping indicators (clip, strip, keep), resulting in 6 core combinations. On top of that, you can specify explicit indentation levels. Let us break it all down.

  • 6 block scalar combinations (|, |-, |+, >, >-, >+)
  • Indentation indicators (|2, >2, etc.)
  • 3 flow scalar styles (plain, single-quoted, double-quoted)
  • Subtle differences in trailing newline behavior
  • Interaction with YAML parsers (PyYAML, js-yaml, SnakeYAML)

2. Literal Style (|): Preserves Newlines

The literal block scalar (|) preserves every newline in your text exactly as written. Each line break in the YAML source becomes a literal \n in the parsed value. A single trailing newline is added by default (clip behavior).

When to use: shell scripts, code blocks, pre-formatted text, SQL queries, any content where line breaks matter.
# Literal style (|) — preserves newlines exactly
message: |
  Line one.
  Line two.
  Line three.

Parsed result:

"Line one\nLine two\nLine three\n"
# Preserves indentation beyond the block indent level
script: |
  #!/bin/bash
  if [ "$ENV" = "prod" ]; then
    echo "Production mode"
    npm run build
  else
    echo "Development mode"
    npm run dev
  fi
# SQL query — newlines preserved for readability
query: |
  SELECT u.id, u.name, u.email
  FROM users u
  JOIN orders o ON u.id = o.user_id
  WHERE o.created_at > '2025-01-01'
  ORDER BY u.name ASC
  LIMIT 100;
Key points: Indentation is stripped (relative to the block). Leading spaces beyond the indent level are preserved. A trailing newline is added by default.

3. Folded Style (>): Folds Newlines to Spaces

The folded block scalar (>) replaces single newlines with spaces, effectively joining consecutive lines into a single long line. However, blank lines and "more-indented" lines still create real newlines. A single trailing newline is added by default.

When to use: long descriptions, paragraphs of text, any string that you want to wrap for readability but parse as a single line.
# Folded style (>) — joins lines with spaces
description: >
  This is a very long string
  that I want to wrap across
  multiple lines for readability.

Parsed result:

"This is a very long string that I want to wrap across multiple lines for readability.\n"

Blank lines create real newlines in folded style:

# Blank lines create real newlines in folded style
content: >
  Paragraph one.

  Paragraph two.

  Paragraph three.

Parsed result:

"Paragraph one.\nParagraph two.\nParagraph three.\n"

More-indented lines also preserve newlines:

# More-indented lines preserve newlines
content: >
  Regular text
    indented line
    another indented
  back to normal

Parsed result:

"Regular text\n  indented line\n  another indented\nback to normal\n"

4. Chomping Indicators: Strip (-), Keep (+), Clip (default)

Chomping controls what happens to the trailing newlines at the end of a block scalar. This is one of the most misunderstood features of YAML.

Clip (default, no indicator)

Keeps a single trailing newline. Any additional trailing newlines are removed.

# Clip (default) — single trailing newline
message: |
  hello world


# (two blank lines at end)
# Result: "hello world\n"  (one trailing newline)

Strip (- indicator)

Removes ALL trailing newlines. The string ends with the last non-empty line.

# Strip (-) — no trailing newline
message: |-
  hello world


# (two blank lines at end, all removed)
# Result: "hello world"  (no trailing newline)

Keep (+ indicator)

Preserves ALL trailing newlines exactly as written.

# Keep (+) — all trailing newlines preserved
message: |+
  hello world


# (two blank lines at end, all kept)
# Result: "hello world\n\n\n"  (all three trailing newlines kept)

5. All 6 Block Scalar Combinations

Here is a comprehensive reference table showing how each combination of style and chomping indicator behaves:

SyntaxNameInternal NewlinesTrailing NewlinesUse Case
|Literal ClipPreservedSingle \n addedShell scripts, code
|-Literal StripPreservedAll removedInline values, no trailing NL
|+Literal KeepPreservedAll keptPreserving exact whitespace
>Folded ClipFolded to spacesSingle \n addedLong descriptions
>-Folded StripFolded to spacesAll removedClean single-line values
>+Folded KeepFolded to spacesAll keptParagraphs with trailing space

Side-by-side examples with parsed results:

# |  (literal clip) — preserves newlines, adds single trailing \n
a: |
  one
  two

# Result: "one\ntwo\n"

# |- (literal strip) — preserves newlines, no trailing \n
b: |-
  one
  two

# Result: "one\ntwo"

# |+ (literal keep) — preserves newlines, keeps ALL trailing \n
c: |+
  one
  two


# Result: "one\ntwo\n\n"

# >  (folded clip) — folds newlines, adds single trailing \n
d: >
  one
  two

# Result: "one two\n"

# >- (folded strip) — folds newlines, no trailing \n
e: >-
  one
  two

# Result: "one two"

# >+ (folded keep) — folds newlines, keeps ALL trailing \n
f: >+
  one
  two


# Result: "one two\n\n"

6. Indentation Control: |2, >2

By default, YAML determines the indentation level of a block scalar from the first non-empty line. Sometimes you need to override this, especially when content starts with spaces.

Why you might need explicit indentation:

  • Content that starts with spaces (e.g., indented code)
  • YAML parsers that get confused by leading spaces
  • Ensuring consistent parsing across different YAML libraries

Without indentation indicator (auto-detect):

# Auto-detect indentation (default behavior)
# YAML detects 2-space indent from first non-empty line
content: |
  normal text
  more text

With explicit indentation indicator:

# Explicit indentation: |2 means "strip 2 spaces"
# Useful when content starts with spaces
content: |2
    This line has 2 leading spaces after stripping
    This also has 2 leading spaces
  This has 0 leading spaces after stripping

# Result: "  This line has 2 leading spaces after stripping\n  This also has 2 leading spaces\nThis has 0 leading spaces after stripping\n"
# Folded with explicit indentation
description: >2
    This paragraph starts with
    two extra spaces that are
    preserved in the output.

# Result: "  This paragraph starts with two extra spaces that are preserved in the output.\n"
The number after | or > specifies how many spaces of indentation to strip. |2 means "strip 2 spaces of indentation from each line."
# Combining indentation with chomping indicators
script: |2-
    #!/usr/bin/env python
    def main():
        print("Hello")

# Result: "  #!/usr/bin/env python\n  def main():\n      print(\"Hello\")"
# (2 spaces stripped, no trailing newline due to -)

config: >2+
    This is indented content
    that will be folded
    with trailing newlines kept.


# Result: "  This is indented content that will be folded with trailing newlines kept.\n\n"
# (2 spaces stripped, newlines kept due to +)
You can combine indentation with chomping: |2-, >2+, |4+, etc.

7. Real-World Examples

Docker Compose

Multiline commands in Docker Compose services:

# docker-compose.yml
version: "3.8"

services:
  web:
    image: node:20-alpine
    # Literal style — each command on its own line
    command: |
      sh -c "
        npm install &&
        npm run migrate &&
        npm run start
      "
    environment:
      # Folded strip — long value on one line, no trailing newline
      DATABASE_URL: >-
        postgresql://user:password@db:5432/myapp
        ?sslmode=require
        &connect_timeout=10
    healthcheck:
      # Literal strip — script without trailing newline
      test: |-
        curl -f http://localhost:3000/health || exit 1
      interval: 30s
      timeout: 10s

  nginx:
    image: nginx:alpine
    volumes:
      - ./nginx.conf:/etc/nginx/nginx.conf:ro
    # Folded style — long description for documentation
    labels:
      description: >
        This is the reverse proxy service that handles
        SSL termination, load balancing, and static file
        serving for the web application.

Kubernetes ConfigMap & Pod

Storing configuration files and scripts in ConfigMaps:

# ConfigMap with embedded configuration files
apiVersion: v1
kind: ConfigMap
metadata:
  name: app-config
data:
  # Literal style — preserve the entire nginx config as-is
  nginx.conf: |
    server {
        listen 80;
        server_name example.com;

        location / {
            proxy_pass http://app:3000;
            proxy_set_header Host $host;
            proxy_set_header X-Real-IP $remote_addr;
        }

        location /static {
            root /var/www;
            expires 30d;
        }
    }

  # Literal style — init script
  init.sh: |
    #!/bin/bash
    set -euo pipefail
    echo "Initializing database..."
    psql -U "$POSTGRES_USER" -d "$POSTGRES_DB" <<'SQL'
      CREATE TABLE IF NOT EXISTS users (
        id SERIAL PRIMARY KEY,
        name TEXT NOT NULL,
        email TEXT UNIQUE
      );
    SQL
    echo "Database initialized."

---
# Pod with multiline command
apiVersion: v1
kind: Pod
metadata:
  name: debug-pod
  annotations:
    # Folded style — long description
    description: >
      This pod is used for debugging network
      connectivity issues in the cluster. It
      includes curl, nslookup, and ping tools.
spec:
  containers:
    - name: debug
      image: busybox
      # Literal strip — command without trailing newline
      command: ["sh", "-c"]
      args:
        - |-
          echo "Starting diagnostics..."
          nslookup kubernetes.default
          curl -s -o /dev/null -w "%{http_code}" http://app-service:8080/health
          echo "Diagnostics complete."

GitHub Actions

Multi-step shell scripts in workflow steps:

# .github/workflows/ci.yml
name: CI Pipeline

on:
  push:
    branches: [main]
  pull_request:
    branches: [main]

jobs:
  test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - name: Setup and Test
        # Literal style — multi-line shell script
        run: |
          echo "Installing dependencies..."
          npm ci
          echo "Running linter..."
          npm run lint
          echo "Running tests..."
          npm test -- --coverage
          echo "All checks passed!"

      - name: Build Docker Image
        run: |
          docker build \
            --build-arg NODE_ENV=production \
            --build-arg VERSION=${{ github.sha }} \
            -t myapp:${{ github.sha }} \
            -t myapp:latest \
            .

      - name: Set environment variables
        # Literal style — setting multi-line env vars
        run: |
          echo "DEPLOY_MESSAGE<<EOF" >> $GITHUB_ENV
          echo "Deployed commit ${{ github.sha }}" >> $GITHUB_ENV
          echo "Branch: ${{ github.ref_name }}" >> $GITHUB_ENV
          echo "Author: ${{ github.actor }}" >> $GITHUB_ENV
          echo "EOF" >> $GITHUB_ENV

      - name: Notify
        if: failure()
        uses: slackapi/slack-github-action@v1
        with:
          # Folded strip — JSON payload on one line
          payload: >-
            {
              "text": "CI failed for ${{ github.repository }}",
              "blocks": [
                {
                  "type": "section",
                  "text": {
                    "type": "mrkdwn",
                    "text": "Build failed on ${{ github.ref_name }}"
                  }
                }
              ]
            }

Ansible Playbooks

Shell commands and templates in Ansible tasks:

# playbook.yml
---
- name: Deploy application
  hosts: webservers
  become: yes
  tasks:
    - name: Create deployment script
      # Literal style — shell script content
      copy:
        dest: /opt/deploy.sh
        mode: '0755'
        content: |
          #!/bin/bash
          set -euo pipefail

          APP_DIR="/opt/myapp"
          BACKUP_DIR="/opt/backups"

          echo "[$(date)] Starting deployment..."

          # Backup current version
          if [ -d "$APP_DIR" ]; then
            cp -r "$APP_DIR" "$BACKUP_DIR/$(date +%Y%m%d_%H%M%S)"
          fi

          # Pull and restart
          cd "$APP_DIR"
          git pull origin main
          docker compose down
          docker compose up -d --build

          echo "[$(date)] Deployment complete."

    - name: Configure systemd service
      copy:
        dest: /etc/systemd/system/myapp.service
        content: |
          [Unit]
          Description=My Application
          After=docker.service
          Requires=docker.service

          [Service]
          Type=oneshot
          RemainAfterExit=yes
          WorkingDirectory=/opt/myapp
          ExecStart=/usr/bin/docker compose up -d
          ExecStop=/usr/bin/docker compose down

          [Install]
          WantedBy=multi-user.target

    - name: Display deployment summary
      debug:
        # Folded style — long message
        msg: >
          Deployment completed successfully on
          {{ inventory_hostname }}. The application
          is now running on port {{ app_port }}.
          Check the logs at /var/log/myapp.log
          for any issues.

8. Flow Scalars: Plain, Single-Quoted, Double-Quoted

Besides block scalars (| and >), YAML also has flow scalars that handle multiline strings differently. These are important to understand for completeness.

Plain Scalars (no quotes)

Unquoted strings. Newlines are folded to spaces (like > style). Cannot contain special characters at the start (: # [ ] { } etc.).

# Plain scalar — no quotes, newlines folded to spaces
description: This is a long
  string that spans multiple
  lines in the YAML source.

# Result: "This is a long string that spans multiple lines in the YAML source."

Single-Quoted Scalars

Enclosed in single quotes. Newlines are folded to spaces. No escape sequences (\n is literal). Use '' to include a literal single quote.

# Single-quoted — no escape sequences, newlines folded
message: 'This is a long
  string with ''escaped'' single
  quotes inside it.'

# Result: "This is a long string with 'escaped' single quotes inside it."
# Note: \n is NOT interpreted — it stays as literal characters
path: 'C:\Users\name\docs'
# Result: "C:\Users\name\docs" (backslashes are literal)

Double-Quoted Scalars

Enclosed in double quotes. Newlines are folded to spaces. Supports escape sequences (\n, \t, \\, \", \uXXXX). Use \\ for literal backslash.

# Double-quoted — supports escape sequences
message: "Line one\nLine two\nLine three"
# Result: "Line one\nLine two\nLine three" (real newlines!)

# Newlines in source are folded to spaces
long_message: "This is a long
  string that wraps across
  lines in the source."
# Result: "This is a long string that wraps across lines in the source."

# Use \\ for literal backslash
path: "C:\\Users\\name\\docs"
# Result: "C:\Users\name\docs"

# Unicode escapes
emoji: "\u2764 \u2728"
# Result: "heart sparkles" (actual unicode characters)

Flow scalar comparison:

StyleEscape SequencesNewline HandlingSpecial Characters
PlainNoFolded to spacesLimited (no : # at start)
Single-quotedNoFolded to spacesAll allowed (use \'\')
Double-quotedYesFolded to spacesAll allowed

9. Decision Flowchart: Which Style to Use

Use this quick reference to pick the right multiline string style:

Q1: Does your string contain literal newlines that must be preserved?

Yes -> Use literal style |

No -> Continue to next question

Q2: Is it a long paragraph that you want to wrap for readability?

Yes -> Use folded style >

No -> Use a flow scalar (plain, single, or double quoted)

Q3: Do you need a trailing newline?

Yes -> Use clip (default) or keep (+)

No -> Use strip (-)

Q4: Does your content start with spaces?

Yes -> Add explicit indentation: |2 or >2

No -> Let YAML auto-detect indentation

Q5: Do you need escape sequences (\n, \t)?

Yes -> Use double-quoted flow scalar

No -> Use plain or single-quoted scalar

Quick summary:

# Shell scripts / code blocks       ->  | (literal)
# Long descriptions / paragraphs    ->  > (folded)
# No trailing newline needed         ->  add - (strip)
# Keep all trailing newlines         ->  add + (keep)
# Content starts with spaces         ->  add N (e.g., |2, >2)
# Single-line, no special chars      ->  plain scalar
# Need escape sequences (\n, \t)    ->  "double quoted"
# String with special YAML chars     ->  'single quoted' or "double"
  • Shell scripts / code blocks -> | (literal)
  • Long descriptions / paragraphs -> > (folded)
  • Single-line with no special chars -> plain scalar
  • String with special characters -> "double quoted"
  • No trailing newline needed -> add - (strip)

10. FAQ

What is the difference between | and > in YAML?

| (literal) preserves all newlines exactly as written. > (folded) replaces single newlines with spaces, joining lines into a paragraph. Both add a single trailing newline by default. Use | for scripts and code; use > for long descriptions.

How do I remove the trailing newline from a YAML block scalar?

Add a strip indicator (-) after the style character: |- or >-. This removes ALL trailing newlines from the parsed string. For example, "message: |-\n Hello world" parses to "Hello world" (no trailing \n).

Can I mix | and > styles in the same YAML file?

Yes, absolutely. Each key can use a different scalar style. For example, you might use | for a shell script and > for a description in the same Kubernetes manifest or Docker Compose file.

What does the number mean in |2 or >2?

The number is an explicit indentation indicator. It tells the YAML parser how many spaces of indentation to strip from each line. This is useful when your content starts with spaces. For example, |2 means strip 2 spaces of leading indentation.

Why does my YAML multiline string have extra spaces or newlines?

Common causes: (1) Using > when you meant | (newlines get folded to spaces). (2) Forgetting the chomping indicator (default clip adds one trailing \n). (3) Inconsistent indentation within the block. (4) Extra blank lines at the end of the block. Use a YAML parser or validator to see exactly how your string is being interpreted.

𝕏 Twitterin LinkedIn
Isso foi útil?

Fique atualizado

Receba dicas de dev e novos ferramentas semanalmente.

Sem spam. Cancele a qualquer momento.

Try These Related Tools

Y{}JSON ↔ YAML ConverterTYTOML ↔ YAMLYMLYAML Validator & Formatter🐳Docker Compose Generator

Related Articles

Sintaxe YAML e validação: Erros comuns e como corrigi-los

Domine a sintaxe YAML: regras de indentação, erros de parsing, tipos de dados e melhores práticas.

YAML Âncoras, Aliases e Merge Keys

Domine princípios DRY do YAML com âncoras, aliases e merge keys. Reduza duplicação em Docker Compose e CI/CD.