Heredoc and Nowdoc

Ma Mariya Vinciya Updated 21 Oct 2026
10 min read ·Lesson 158 of 174
In this post

What Are Heredoc and Nowdoc? In simple terms: Heredoc and nowdoc are ways to write long, multi-line strings without wrapping them in quotes. You mark the start and end with a name of your choice, and

What Are Heredoc and Nowdoc?

In simple terms: Heredoc and nowdoc are ways to write long, multi-line strings without wrapping them in quotes. You mark the start and end with a name of your choice, and everything in between becomes the string.

Technically:

  • Heredoc (<<<IDENTIFIER) behaves like a double-quoted string: variables are interpolated and escape sequences such as \n and \t are processed.
  • Nowdoc (<<<'IDENTIFIER') behaves like a single-quoted string: nothing is interpolated and no escape sequences are processed — the text is taken exactly as written.

Why They Exist

Long text in ordinary quotes quickly becomes hard to manage:

<?php
$name = "Anu";
$html = "<div class=\"card\">\n  <h2 class=\"title\">Welcome, $name</h2>\n  <p>Thanks for joining.</p>\n</div>";

Every inner double quote must be escaped, line breaks are written as \n, and the structure is hard to see. With heredoc:

<?php
$name = "Anu";
$html = <<<HTML
<div class="card">
  <h2 class="title">Welcome, $name</h2>
  <p>Thanks for joining.</p>
</div>
HTML;

The text looks exactly like the output. Quotes need no escaping, and line breaks are real line breaks.

Typical uses:

  • HTML fragments and email templates
  • SQL queries
  • Plain-text emails, reports, and command-line help messages
  • JSON or XML samples in tests
  • Generated code or configuration files

Heredoc Syntax

Structure

$variable = <<<IDENTIFIER
Text line 1
Text line 2 with $variables
IDENTIFIER;
PartRule
<<<Starts the heredoc
IDENTIFIERAny name following variable naming rules (letters, digits, underscores; not starting with a digit). By convention, uppercase.
BodyStarts on the line after the opening identifier
Closing identifierThe same name, on its own line (it may be indented — see below)

The identifier can also be written in double quotes (<<<"IDENTIFIER"), which behaves identically to the unquoted form.

Example

<?php
$customer = "Ramesh";
$orderId = 7781;
$items = 3;

$message = <<<TEXT
Hello $customer,

Your order #$orderId with $items items has been shipped.
Track it any time from your account page.

Regards,
GoShop Team
TEXT;

echo $message;

Output

Hello Ramesh,

Your order #7781 with 3 items has been shipped.
Track it any time from your account page.

Regards,
GoShop Team

How It Works

  1. <<<TEXT begins the string. The string content starts on the next line.
  2. Variables ($customer, $orderId, $items) are interpolated, just as in double quotes.
  3. Blank lines and line breaks are preserved exactly.
  4. The line containing only TEXT; ends the string. The semicolon belongs to the PHP statement ($message = ...;), not to the heredoc.
  5. The newline directly before the closing identifier is not part of the string. The value ends with GoShop Team, without a trailing newline.

Interpolation and Escapes in Heredoc

All interpolation forms from double-quoted strings work in heredoc.

<?php
$user = ['name' => 'Kala', 'points' => 1250];
$date = new DateTime('2026-10-08');

echo <<<TEXT
Name:\t{$user['name']}
Points:\t{$user['points']}
Date:\t{$date->format('d/m/Y')}
Price shown as: \$99 "including tax"
TEXT;

Output

Name:	Kala
Points:	1250
Date:	08/10/2026
Price shown as: $99 "including tax"
  • {$user['name']} and {$date->format(...)} use the curly-brace syntax.
  • \t becomes a tab.
  • \$ produces a literal dollar sign.
  • Double quotes need no escaping.

Function calls and constants are not interpolated (the same limitation as double-quoted strings), so prepare such values in variables first.


Nowdoc Syntax

Structure

$variable = <<<'IDENTIFIER'
Raw text — nothing is processed
IDENTIFIER;

The only difference from heredoc is the single quotes around the opening identifier.

Example

<?php
$name = "Kala";

echo <<<'CODE'
<?php
$name = "World";
echo "Hello, $name!\n";
CODE;

Output

<?php
$name = "World";
echo "Hello, $name!\n";

$name is not replaced (even though a $name variable exists), and \n stays as a backslash followed by n. Nowdoc is the right choice whenever the text itself contains $, backslashes, or code — such as code samples in documentation, shell scripts, or regular expressions.

Heredoc vs Nowdoc

FeatureHeredoc <<<IDNowdoc <<<'ID'
Behaves likeDouble-quoted stringSingle-quoted string
Variables interpolatedYesNo
Escape sequences (\n, \t, \$)ProcessedNot processed (not even 0)
Need to escape "NoNo
Best forTemplates with valuesLiteral text, code, patterns, SQL

Indentation of the Closing Identifier (PHP 7.3+)

The Rule

Since PHP 7.3, the closing identifier may be indented. PHP then removes that same amount of indentation from every line of the body. This allows heredoc to be indented naturally inside functions and classes.

Example

<?php
function welcomeEmail(string $name, string $plan): string
{
    return <<<TEXT
        Hi $name,

        Thanks for upgrading to the $plan plan.
          - Priority support
          - Unlimited projects

        Team GoCourse
        TEXT;
}

echo welcomeEmail("Asha", "Pro");

Output

Hi Asha,

Thanks for upgrading to the Pro plan.
  - Priority support
  - Unlimited projects

Team GoCourse

How It Works

  • The closing TEXT; is indented by 8 spaces.
  • PHP removes 8 spaces from the start of every body line.
  • Lines indented by 10 spaces (the list items) keep their extra 2 spaces, so relative indentation is preserved.
  • Empty lines are allowed and stay empty.

Indentation Errors

If any body line is indented less than the closing identifier, PHP cannot remove the required amount and reports a parse error:

<?php
$text = <<<TEXT
      Indented line
  Less indented line
      TEXT;
PHP Parse error:  Invalid body indentation level (expecting an indentation level of at least 6)

Mixing tabs and spaces in the indentation of the body and the closing identifier is also an error. Use spaces consistently.

Before PHP 7.3

In PHP 7.2 and older, the closing identifier had to be at the very start of the line (column 0) and followed only by a semicolon and a newline. You may still see this style in older code; it remains valid today.


Using Heredoc and Nowdoc Inside Expressions

Since PHP 7.3, the closing identifier can be followed by other code on the same line, so heredoc and nowdoc can be used as function arguments and array elements.

Example: As a Function Argument

<?php
$product = "USB Hub";
$price = 899;

printf(<<<'FORMAT'
    Product: %s
    Price:   ₹%.2f
    FORMAT, $product, $price);

Output

Product: USB Hub
Price:   ₹899.00

How It Works

  • A nowdoc is used for the format string so that PHP does not try to interpret anything inside it; printf() processes the %s and %.2f placeholders.
  • FORMAT, closes the nowdoc and continues the argument list on the same line.
  • The closing identifier is indented 4 spaces, so 4 spaces are removed from each line.

Practical Example: SQL Queries

Long SQL queries are much easier to read when written across several lines.

<?php
$sql = <<<'SQL'
    SELECT p.id, p.name, p.price, c.name AS category
    FROM products AS p
    JOIN categories AS c ON c.id = p.category_id
    WHERE p.price <= :max_price
      AND p.is_active = 1
    ORDER BY p.price ASC
    LIMIT 20
    SQL;

// $stmt = $pdo->prepare($sql);
// $stmt->execute(['max_price' => 5000]);

echo $sql;

Output

SELECT p.id, p.name, p.price, c.name AS category
FROM products AS p
JOIN categories AS c ON c.id = p.category_id
WHERE p.price <= :max_price
  AND p.is_active = 1
ORDER BY p.price ASC
LIMIT 20

Why Nowdoc and Placeholders

  • Nowdoc guarantees that nothing in the SQL is interpolated. A query should never contain interpolated user input, because that allows SQL injection.
  • :max_price is a named placeholder. The actual value is sent separately with execute(), so the database never confuses data with SQL code. Prepared statements are covered in PDO Prepared Statements.
  • Indenting the query inside the code while removing the indentation from the result keeps both the code and logged queries readable.

Practical Example: HTML Email Template

<?php
function orderConfirmationHtml(array $order): string
{
    $name = htmlspecialchars($order['customer']);
    $total = number_format($order['total'], 2);
    $rows = '';

    foreach ($order['items'] as $item) {
        $itemName = htmlspecialchars($item['name']);
        $rows .= "<tr><td>$itemName</td><td>{$item['qty']}</td></tr>\n";
    }

    return <<<HTML
        <h1>Thank you, $name!</h1>
        <p>Your order <strong>#{$order['id']}</strong> is confirmed.</p>
        <table>
        $rows</table>
        <p>Total paid: ₹$total</p>
        HTML;
}

echo orderConfirmationHtml([
    'id' => 3301,
    'customer' => 'Vijay & Co',
    'total' => 2780,
    'items' => [
        ['name' => 'Desk Organizer', 'qty' => 2],
        ['name' => 'Cable Clips', 'qty' => 5],
    ],
]);

Output

<h1>Thank you, Vijay & Co!</h1>
<p>Your order <strong>#3301</strong> is confirmed.</p>
<table>
<tr><td>Desk Organizer</td><td>2</td></tr>
<tr><td>Cable Clips</td><td>5</td></tr>
</table>
<p>Total paid: ₹2,780.00</p>

Step-by-Step Explanation

  1. All dynamic values are prepared before the heredoc: names are escaped with htmlspecialchars() (note how & becomes &), and the total is formatted with number_format().
  2. Table rows are built in a loop and collected in $rows, each ending with a newline.
  3. The heredoc contains only the layout and simple variable references, so the template remains easy to read and edit.
  4. $rows</table> places the closing tag directly after the generated rows. Because the closing HTML; is indented 8 spaces, that indentation is removed from the template lines. The generated rows are inserted as variable content and are not affected by indentation removal.

Preparing values first and keeping the template simple is the same separation of logic and presentation recommended in PHP and HTML.


Heredoc and Nowdoc in Constants and Properties

Both can be used to initialize class constants and property defaults, as long as they contain no variables (the value must be known at compile time):

<?php
class CliHelp
{
    public const USAGE = <<<'TXT'
        Usage: php import.php [options] <file>

        Options:
          --dry-run   Validate without saving
          --verbose   Show every processed row
        TXT;
}

echo CliHelp::USAGE;

Output

Usage: php import.php [options] <file>

Options:
  --dry-run   Validate without saving
  --verbose   Show every processed row

Nowdoc is used because <file> and the option text should appear exactly as written.


Choosing an Identifier

The closing identifier ends the string wherever it appears at the start of a line (after optional indentation) and is not followed by another identifier character. If your text contains a line beginning with the same word, the string ends early and the rest becomes a parse error.

<?php
// Risky: a body line starts with "END"
$text = <<<END
The game is over.
END of transmission
END;

Here PHP treats END at the start of the second body line as the closing identifier, and of transmission becomes invalid code.

Use descriptive, unlikely identifiers that describe the content: HTML, SQL, TEXT, JSON, EMAIL, EOT (end of text). Editors such as PhpStorm also use identifiers like HTML and SQL to apply syntax highlighting inside the string.


Common Mistakes

Text on the Same Line as the Opening Identifier

$text = <<<TEXT Hello
TEXT;

Effect: Parse error. The body must start on the next line.

Fix: Put a line break right after <<<TEXT.

Body Indented Less Than the Closing Identifier

Effect: Invalid body indentation level.

Fix: Indent every body line at least as much as the closing identifier, or move the closing identifier left.

Expecting a Trailing Newline

Effect: Joined output runs together, because the last newline before the closing identifier is not included.

Fix: Add an empty line before the closing identifier, or append PHP_EOL / "\n".

Using Heredoc When the Text Contains $

Effect: Unintended interpolation or "Undefined variable" warnings in code samples, shell scripts, or prices.

Fix: Use nowdoc for literal text.

Interpolating Unescaped User Input

Effect: XSS in HTML templates, injection in SQL.

Fix: Escape values before the heredoc (htmlspecialchars()), and use placeholders for SQL.

Choosing a Common Word as the Identifier

Effect: The string ends early when a line starts with that word.

Fix: Use a distinct identifier such as HTML, SQL, or EOT.


Best Practices

  • Use heredoc for multi-line templates that include variables; use nowdoc for literal text.
  • Indent the closing identifier to match your code (PHP 7.3+) and keep body indentation consistent with spaces.
  • Prepare escaped and formatted values in variables before the heredoc; keep the heredoc itself simple.
  • Use nowdoc with placeholders for SQL — never interpolate data into queries.
  • Pick identifiers that describe the content and will not appear at the start of a body line.

Official reference: https://www.php.net/manual/en/language.types.string.php#language.types.string.syntax.heredoc

0 Comments

Reviewed before they appear

No comments yet.

PHP
PHP Home PHP Switch PHP Match Alternative Syntax PHP Loops While Loop Do While Loop For Loop Foreach Loop Nested Loops Break Statement PHP Intro PHP Install PHP Built-in Web Server PHP Syntax PHP Tags PHP and HTML PHP Comments PHP Multiline Comments PHP Variables Variables Scope PHP Casting Logical Operators Null Coalescing Operator Spaceship Operator Operator Precedence Continue Statement Modify Strings PHP If...Else...Elseif PHP If Modify Strings PHP If Operators PHP Functions Concatenate Strings PHP If...Else User-defined Functions Slicing Strings PHP Shorthand if Escape Characters Default Argument Values PHP Nested if Pass by Reference String Interpolation Named Arguments PHP File Upload Variadic Functions Return Values Search in Strings Multibyte Strings Type Declarations in Functions PHP Numbers PHP Math PHP Floats PHP Random Numbers PHP Constants PHP Properties PHP Methods PHP Constructor Constructor Property Promotion PHP Cookies PHP Destructor PHP Access Modifiers Readonly Properties PHP Sessions PHP Inheritance Method Overriding PHP Filters The final Keyword CSRF Protection PHP Filters Advanced Session Security Secure Cookies PHP Magic Constants File Upload Security PHP Callback Functions PHP Operators Filesystem Security PHP JSON Arithmetic Operators Error Reporting in Production Hiding PHP Assignment Operators PHP Configuration (php.ini) Comparison Operators PHP Command Line (CLI) PHP Exceptions Increment / Decrement Operators Migrating to PHP 8 PHP Form Validation PHP Form Required PHP Form URL/E-mail Error Handling Form Input Sanitizing Escaping Output (htmlspecialchars) Custom Error Handler Checkbox, Radio and Select Inputs Sticky Forms PHP Form Complete PHP Date and Time Date Formatting Error Logging HTTP Headers and Redirects Output Buffering Sending Email PHP cURL Consuming REST APIs PHP Generators AJAX PHP AJAX Database AJAX XML AJAX Live Search AJAX Poll AJAX with JSON Security Introduction Password Hashing SQL Injection Prevention XSS Prevention Garbage Collection Project: Simple Blog PHP What is OOP PHP Classes/Objects The $this Keyword Access Array Items Update Array Items Add Array Items Remove Array Items Sorting Arrays Multidimensional Arrays Array Destructuring Array Spread Operator Search in Arrays Merge Arrays PHP Abstract Classes PHP Interfaces Polymorphism PHP Traits PHP Static Methods PHP Static Properties Late Static Binding Magic Methods Object Cloning MySQL Database MySQL Connect MySQL Create DB MySQL Create Table MySQL Insert Data MySQL Get Last ID MySQL Insert Multiple MySQL Prepared MySQL Select Data MySQL Where Debugging PHP Project: Image Upload Gallery $_SESSION $_ENV PHP RegEx RegEx Patterns Heredoc and Nowdoc RegEx Modifiers preg_match preg_replace preg_split PHP RegEx Functions Nullable Types Mixed and Never Types Nullsafe Operator PHP Attributes PHP Fibers Readonly Classes Property Hooks (PHP 8.4) Asymmetric Visibility (PHP 8.4) Reflection API SPL Data Structures Composer
Ask about this post
AI Ask about this post

Ask questions about Heredoc and Nowdoc and get answers drawn from it.

Signed-in readers only.