Heredoc and Nowdoc
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\nand\tare 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;
| Part | Rule |
|---|---|
<<< | Starts the heredoc |
IDENTIFIER | Any name following variable naming rules (letters, digits, underscores; not starting with a digit). By convention, uppercase. |
| Body | Starts on the line after the opening identifier |
| Closing identifier | The 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
<<<TEXTbegins the string. The string content starts on the next line.- Variables (
$customer,$orderId,$items) are interpolated, just as in double quotes. - Blank lines and line breaks are preserved exactly.
- The line containing only
TEXT;ends the string. The semicolon belongs to the PHP statement ($message = ...;), not to the heredoc. - 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.\tbecomes 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
| Feature | Heredoc <<<ID | Nowdoc <<<'ID' |
|---|---|---|
| Behaves like | Double-quoted string | Single-quoted string |
| Variables interpolated | Yes | No |
Escape sequences (\n, \t, \$) | Processed | Not processed (not even 0) |
Need to escape " | No | No |
| Best for | Templates with values | Literal 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%sand%.2fplaceholders. 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_priceis a named placeholder. The actual value is sent separately withexecute(), 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
- All dynamic values are prepared before the heredoc: names are escaped with
htmlspecialchars()(note how&becomes&), and the total is formatted withnumber_format(). - Table rows are built in a loop and collected in
$rows, each ending with a newline. - The heredoc contains only the layout and simple variable references, so the template remains easy to read and edit.
$rows</table>places the closing tag directly after the generated rows. Because the closingHTML;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