PHP Heredoc and Nowdoc
Most PHP strings are quick one-liners wrapped in single or double quotes, but sometimes you need to build a large block of text — an email body, an HTML fragment, a SQL query, or a formatted report — without drowning in escaped quotes and string concatenation. PHP solves this with two specialized syntaxes: heredoc, which behaves like a multi-line double-quoted string with full variable interpolation, and nowdoc, which behaves like a multi-line single-quoted string with no interpolation at all. Mastering both makes long, readable strings far easier to write and maintain.
Overview: How Heredoc and Nowdoc Work
Heredoc and nowdoc are PHP’s syntaxes for writing multi-line string literals directly in your source code, without closing and reopening quotes or gluing lines together with the . operator. A heredoc begins with three less-than signs <<< followed by an identifier of your choosing (by convention written in uppercase, such as EOT, HTML, or SQL), and ends on a line containing that same identifier. Everything between the opening line and the closing identifier becomes the string’s contents, including the line breaks exactly as you typed them. Internally, the Zend Engine’s tokenizer treats heredoc content almost identically to a double-quoted string: it scans for $variable, {$expression}, and ${expression} interpolation markers and for backslash escape sequences like \n and \t, expanding them at compile time into the final string value. A nowdoc is the raw, non-interpolating sibling of heredoc — you create one by wrapping the opening identifier in single quotes, e.g. <<<'EOT'. Because nowdoc content is never scanned for variables or escape sequences, PHP treats it much like a single-quoted string: what you type is exactly what you get, character for character. This makes nowdoc the right tool whenever your literal text contains dollar signs, backslashes, or other characters you don’t want PHP to interpret — shell scripts, regular expressions, or code samples embedded as plain text are classic cases. Both constructs solve the same underlying problem: readability. A large string built from dozens of concatenated fragments and escaped quotes is error-prone and hard to scan visually; heredoc and nowdoc let you write the text block almost exactly as it will appear, indentation and all, while PHP handles the escaping bookkeeping.
Syntax
$variable = <<<IDENTIFIER
Multi-line text here.
It can reference $anotherVariable or {$object->property}.
IDENTIFIER;
The nowdoc form looks identical except the identifier is wrapped in single quotes:
$variable = <<<'IDENTIFIER'
Literal text here — $signs and \backslashes are left untouched.
IDENTIFIER;
- <<< — the three-character token that starts either construct.
- IDENTIFIER — any valid PHP identifier (letters, digits, underscores, not starting with a digit). Wrap it in single quotes for nowdoc; leave it bare or wrap it in double quotes for heredoc.
- Body — one or more lines of text: interpolated for heredoc, taken literally for nowdoc.
- Closing line — a line containing only optional leading whitespace, then the exact same identifier, optionally followed immediately by
;or,(since PHP 7.3, heredoc and nowdoc can be used directly as a function argument or array value).
Since PHP 7.3, the closing identifier’s indentation is significant: PHP strips that many leading whitespace characters from every line of the body, which lets you indent the whole block to match your surrounding code.
| Feature | Heredoc | Nowdoc |
|---|---|---|
| Opening syntax | <<<EOT |
<<<'EOT' |
| Variable interpolation | Yes | No |
Escape sequences (\n, \t, …) |
Yes | No |
| Behaves like | Double-quoted string | Single-quoted string |
| Added in | PHP 4 | PHP 5.3 |
Examples
Example 1: Interpolated Heredoc for a Bio
<?php
$name = "Maria";
$age = 29;
$role = "Backend Developer";
$bio = <<<BIO
Name: $name
Age: $age
Role: $role
Bio: {$name} has been coding for over five years.
BIO;
echo $bio;
Output:
Name: Maria
Age: 29
Role: Backend Developer
Bio: Maria has been coding for over five years.
This heredoc behaves exactly like a double-quoted string: $name, $age, and $role are replaced with their values, and {$name} uses the curly-brace complex syntax purely for clarity here — it works identically to the bare $name form for a simple variable, but curly braces become mandatory once you interpolate something more complex, such as a method call or a quoted array key.
Example 2: Nowdoc for Literal Text
<?php
$price = 42;
$template = <<<'TPL'
Total due: $price
Use the format: ${variable_name} in Bash scripts.
No interpolation happens here.
TPL;
echo $template;
Output:
Total due: $price
Use the format: ${variable_name} in Bash scripts.
No interpolation happens here.
Because the identifier is wrapped in single quotes ('TPL' instead of TPL), this is a nowdoc, and PHP performs zero interpolation. The dollar signs in $price and ${variable_name} print exactly as typed instead of being treated as PHP variables — exactly what you want when the literal text is a template for another language, like Bash, or a code sample that happens to look like PHP.
Example 3: Flexible Indentation and Object Interpolation
<?php
class Product {
public function __construct(
public readonly string $name,
public readonly float $price,
) {}
}
function renderInvoiceLine(string $line): string {
return "[INVOICE] {$line}";
}
$product = new Product("Wireless Mouse", 19.99);
$quantity = 3;
$subtotal = $product->price * $quantity;
$line = <<<LINE
Product: {$product->name}
Unit price: \${$product->price}
Quantity: {$quantity}
Subtotal: \${$subtotal}
LINE;
echo renderInvoiceLine($line);
Output:
[INVOICE] Product: Wireless Mouse
Unit price: $19.99
Quantity: 3
Subtotal: $59.97
Two PHP 7.3+ features are on display here. First, the closing marker LINE; is indented with four spaces to match the surrounding code, so PHP strips exactly four leading spaces from every line of the body — that’s the flexible heredoc/nowdoc syntax. Second, the heredoc is passed directly as a function argument without first being assigned to an intermediate variable. Inside the body, {$product->name} and {$product->price} use complex curly syntax to reach into an object property, and \$ escapes a literal dollar sign immediately before the interpolated price so the output reads $19.99 rather than triggering a second round of interpolation.
Under the Hood: How PHP Parses Heredoc and Nowdoc
When the Zend Engine tokenizes your script, an opening <<< sequence switches the lexer into a dedicated heredoc/nowdoc scanning mode. For nowdoc, the lexer copies bytes verbatim until it finds the closing identifier line — there is no variable-scanning phase at all, which is why nowdoc behaves exactly like a single-quoted string under the hood. For heredoc, the lexer walks the body the same way it walks a double-quoted string: it looks for a bare $name (simple syntax), a {$expression} (complex syntax, which allows method calls, array access with quoted keys, and property chains), and for backslash escape sequences. Each interpolated piece compiles into a concatenation expression, so a heredoc containing Hello $name compiles to essentially the same opcodes as "Hello " . $name. Once PHP 7.3 introduced flexible indentation, a compile-time step measures the whitespace prefix on the closing identifier’s line and removes exactly that many leading whitespace characters from every body line before interpolation happens — which is why mixing tabs and spaces in that indentation throws a fatal invalid indentation error: the engine can’t reliably measure a consistent prefix width when the whitespace characters differ. Once compilation finishes, the result in both cases is an ordinary PHP string; nothing at runtime distinguishes a value that came from a heredoc, a nowdoc, or a quoted literal — the syntax only matters while the file is being parsed.
Common Mistakes
Mistake 1: Mixing Tabs and Spaces in the Closing Marker’s Indentation
The flexible heredoc/nowdoc syntax measures indentation by counting whitespace characters before the closing identifier, then strips that same prefix from every body line. If the body and the closing marker don’t use the exact same kind of whitespace, PHP cannot compute a consistent width and refuses to compile the file:
<?php
$report = <<<EOT
Line one
Line two
EOT;
echo $report;
The second body line is indented with a tab while the first line and the closing marker use spaces, which triggers Fatal error: Invalid indentation - tabs and spaces cannot be mixed. Fix it by using the same whitespace character everywhere in the block:
<?php
$report = <<<EOT
Line one
Line two
EOT;
echo $report;
Output:
Line one
Line two
Mistake 2: Expecting a Nowdoc to Interpolate Variables
Because heredoc and nowdoc look almost identical, it’s easy to forget which one you’re using and expect a nowdoc to substitute a variable’s value:
<?php
$name = "Sam";
$message = <<<'EOT'
Hello, $name!
EOT;
echo $message;
Output:
Hello, $name!
This code is perfectly valid — it simply isn’t doing what the author probably wanted, because the single-quoted identifier makes it a nowdoc. Removing the quotes turns it back into an interpolating heredoc:
<?php
$name = "Sam";
$message = <<<EOT
Hello, $name!
EOT;
echo $message;
Output:
Hello, Sam!
Best Practices
- Use an uppercase, descriptive identifier (
SQL,HTML,EMAIL) so the block delimiter stands out visually from ordinary code. - Prefer nowdoc over heredoc whenever the text is genuinely static — it avoids accidental interpolation bugs and skips the interpolation scan entirely.
- Use the flexible (PHP 7.3+) indentation form so your heredoc block can be indented along with the surrounding code instead of forcing the closing marker to column one.
- Wrap non-trivial expressions in curly braces —
{$obj->method()},{$arr['key']}— rather than relying on simple syntax, which cannot resolve method calls and can misbehave with quoted array keys. - Escape a literal dollar sign in heredoc with
\$when you need to display a$without triggering interpolation, or switch to nowdoc if most of the text is literal. - Keep the closing identifier’s indentation consistent (all spaces or all tabs) with the rest of the block to avoid the invalid-indentation fatal error.
- Reach for heredoc or nowdoc only for genuinely multi-line content; a short one-liner is clearer as a normal quoted string.
Practice Exercises
- Write a PHP script with an associative array of three products (name, price, quantity). Use a
foreachloop to build up a string of line items, then use a heredoc to wrap them in a header and footer for a printable receipt. - Store a literal regular expression pattern such as
/^\$?[0-9]+\.[0-9]{2}$/in a nowdoc instead of a heredoc, and write one sentence explaining why nowdoc is the safer choice for this value. - Take a block of code that builds an HTML snippet with five or more concatenated strings and quotes, and refactor it into a single flexible heredoc. Note what changes were needed to the quote characters and variable references.
Summary
- Heredoc (
<<<EOT) writes multi-line strings with full variable interpolation, like a multi-line double-quoted string. - Nowdoc (
<<<'EOT') writes multi-line strings with zero interpolation, like a multi-line single-quoted string. - The closing identifier must appear alone on its line (aside from whitespace and an optional trailing
;or,) and must exactly match the opening identifier. - Since PHP 7.3, the closing marker’s indentation is stripped from every body line, and heredoc/nowdoc can be used directly as function arguments or array values.
- Complex interpolation — object properties, method calls, quoted array keys — requires curly-brace syntax like
{$obj->prop}. - Mixing tabs and spaces in the indentation of a flexible heredoc/nowdoc is a fatal error; keep the whitespace consistent.
- Choose nowdoc by default for literal text, and heredoc only when you actually need interpolation.
