Output Buffering

JA JASWANT Updated 17 Oct 2026
6 min read ·Lesson 105 of 109
In this post

What Is Output Buffering? In simple terms: Normally, everything a PHP script prints with echo , print , or HTML outside <?php ?> is sent toward the browser right away. Output buffering tells PHP

What Is Output Buffering?

In simple terms: Normally, everything a PHP script prints with echo, print, or HTML outside <?php ?> is sent toward the browser right away. Output buffering tells PHP to collect the output in memory first. You can then send it later, change it, store it in a variable, or throw it away.

Technically: The output control functions (ob_*) manage a stack of output buffers. ob_start() pushes a new buffer; output then goes into the topmost buffer instead of the next layer. Functions such as ob_get_clean() retrieve and remove a buffer, and ob_end_flush() passes its content to the next layer (or to the client). A buffer can also have a callback that transforms its content.

Why It Is Useful

UseExample
Capturing output as a stringRendering a template into a variable for a layout or an email
Sending headers after output has startedA redirect decided after part of a page was generated
Transforming outputReplacing placeholders, minifying HTML
Discarding outputIgnoring output from code you cannot change
Compressing outputGzip compression with ob_gzhandler

Capturing Output

Example

<?php
ob_start();

echo "Hello";
print " from PHP";
?>
 and from HTML
<?php
$output = ob_get_clean();

echo strtoupper(trim($output)) . "\n";

Output

HELLO FROM PHP AND FROM HTML

How It Works

  1. ob_start() begins buffering.
  2. echo, print, and the HTML between the PHP blocks all go into the buffer — nothing is sent. (The newline directly after ?> is removed by PHP, so the buffer contains Hello from PHP and from HTML followed by a newline.)
  3. ob_get_clean() returns the buffered text and removes the buffer.
  4. The captured text is now an ordinary string that can be modified before printing.

Core Functions

FunctionWhat It Does
ob_start($callback = null)Start a new buffer
ob_get_contents()Return the current buffer's content (buffer stays active)
ob_get_clean()Return the content and remove the buffer
ob_end_clean()Discard the content and remove the buffer
ob_end_flush()Send the content to the next layer and remove the buffer
ob_flush() / ob_clean()Send / discard the content but keep the buffer active
ob_get_level()Number of active buffers
ob_get_length()Size of the current buffer in bytes

Nested Buffers

Buffers form a stack. Output always goes to the topmost buffer:

<?php
echo ob_get_level() . "\n";   // command line: 0

ob_start();
echo "outer ";

ob_start();
echo "inner";
$inner = ob_get_clean();      // closes the inner buffer

echo "[" . strtoupper($inner) . "]";
$outer = ob_get_clean();      // closes the outer buffer

echo $outer . "\n";

Output

0
outer [INNER]

The inner buffer's content was captured separately and then added to the outer buffer in modified form. This is how template engines render a page inside a layout: the page is captured first, then inserted into the layout.

Note: On web servers, ob_get_level() often starts at 1, because the output_buffering setting in php.ini (commonly 4096 bytes) creates a buffer automatically. In the command line, this setting is off, so the level starts at 0. Code should not assume a specific starting level.


Transforming Output With a Callback

A callback receives the buffered content and returns the version that is actually sent:

<?php
ob_start(fn(string $buffer): string => str_replace('{{year}}', '2026', $buffer));

echo "© {{year}} GoCourse\n";

ob_end_flush();

Output

© 2026 GoCourse

Example: Removing Whitespace Between HTML Tags

<?php
ob_start(fn(string $html): string => preg_replace('/>\s+</', '><', $html));
?>
<ul>
    <li>Pen</li>
    <li>Notebook</li>
</ul>
<?php
ob_end_flush();

Output

<ul><li>Pen</li><li>Notebook</li></ul>

Whitespace between a closing > and the next < is removed, reducing the page size. (Real minification must preserve whitespace inside <pre>, <textarea>, and inline text; dedicated libraries or server-side compression are safer choices.)

Compression

ob_start('ob_gzhandler');

ob_gzhandler compresses the output with gzip when the browser supports it. In most setups, compression is better done by the web server (Nginx gzip, Apache mod_deflate), which also handles static files.


Headers After Output

Headers must be sent before the body. While output is still buffered, nothing has been sent yet, so headers can still be added:

<?php
ob_start();

echo "<p>Processing your order...</p>";

$paymentFailed = true;

if ($paymentFailed) {
    ob_end_clean();                                   // discard the partial page
    header('Location: /checkout.php?error=payment', true, 303);
    exit;
}

ob_end_flush();

The partial output is discarded and a redirect is sent instead. Relying on this is a workaround; a cleaner design decides on redirects before generating any output. It is, however, useful when integrating code that prints output in unexpected places.


Exception-Safe Capturing

If code inside a buffer throws an exception, the buffer stays open unless it is closed explicitly. A helper with finally guarantees that buffers are always cleaned up:

<?php
function capture(callable $callback): string
{
    ob_start();

    try {
        $callback();
        return ob_get_contents();
    } finally {
        ob_end_clean();
    }
}

echo capture(fn() => print("Hi there")) . "\n";

$levelBefore = ob_get_level();

try {
    capture(function (): void {
        echo "partial output";
        throw new RuntimeException('Template error');
    });
} catch (RuntimeException $e) {
    echo "Caught: " . $e->getMessage() . "\n";
}

var_dump(ob_get_level() === $levelBefore);

Output

Hi there
Caught: Template error
bool(true)

How It Works

  1. ob_get_contents() reads the buffer before finally removes it with ob_end_clean().
  2. If the callback throws, the finally block still discards the partial output and closes the buffer, so the buffer level is the same as before.
  3. Without this, the partial output ("partial output") could later appear in the middle of an error page.

Practical Example: A Page With a Layout

<?php
function render(string $template, array $data): string
{
    return capture(function () use ($template, $data): void {
        extract($data, EXTR_SKIP);
        include $template;
    });
}

function capture(callable $callback): string
{
    ob_start();
    try {
        $callback();
        return ob_get_contents();
    } finally {
        ob_end_clean();
    }
}

// templates/page.php
file_put_contents(__DIR__ . '/page.php', '<h1><?= htmlspecialchars($title) ?></h1>' . "\n");

// templates/layout.php
file_put_contents(__DIR__ . '/layout.php', "<html><body>\n<?= \$content ?></body></html>\n");

$content = render(__DIR__ . '/page.php', ['title' => 'PHP Courses']);
echo render(__DIR__ . '/layout.php', ['content' => $content]);

Output

<html><body>
<h1>PHP Courses</h1>
</body></html>

Step-by-Step Explanation

  1. The page template is rendered into a string using output buffering.
  2. That string is passed to the layout template as $content.
  3. The layout is rendered the same way and printed.
  4. $content is printed in the layout without htmlspecialchars() because it is already-escaped HTML produced by our own template; all user data was escaped in the page template.

This two-step rendering is the basis of layouts in template engines such as Blade and Twig.


Streaming Output: flush()

Sometimes you want output sent immediately, for example to show progress during a long task:

<?php
while (ob_get_level() > 0) {
    ob_end_flush();
}

foreach (['Importing products', 'Updating prices', 'Clearing cache'] as $step) {
    echo $step . "...\n";
    flush();
    sleep(1);   // simulated work
}

Closing all buffers and calling flush() pushes output toward the client. Whether the user sees it immediately also depends on the web server and proxies, which may buffer responses themselves (for example, Nginx's fastcgi_buffering). For real-time progress in browsers, Server-Sent Events or background jobs with status polling are more reliable.


Common Mistakes

Leaving Buffers Open After Exceptions

Effect: Partial output appears in unexpected places.

Fix: Close buffers in finally.

Assuming the Initial Buffer Level Is 0

Effect: Code that closes "all" buffers breaks other layers (such as framework buffers).

Fix: Record the level before starting and restore exactly that level.

Using Buffering to Hide Design Problems

Effect: Headers are sent late "because buffering makes it work", hiding a confusing flow.

Fix: Decide on headers and redirects before rendering output.

Buffering Huge Output

Effect: Large exports are held entirely in memory.

Fix: Stream large output directly (php://output, fputcsv()), without buffering.


Best Practices

  • Use output buffering mainly to capture template output as strings.
  • Always pair ob_start() with a closing call, ideally in finally.
  • Prefer server-level compression over ob_gzhandler.
  • Do not rely on buffering to send headers late; structure code so headers come first.
  • Stream large or long-running output instead of buffering it.

Official reference: https://www.php.net/manual/en/book.outcontrol.php

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 Home 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 Slicing Strings 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
Ask about this post
AI Ask about this post

Ask questions about Output Buffering and get answers drawn from it.

Signed-in readers only.