Skip to main content
  1. Languages/
  2. PHP Guides/

Mastering PDF Generation in PHP: A Comprehensive Guide to Libraries and Techniques

Jeff Taakey
Author
Jeff Taakey
21+ Year CTO & Multi-Cloud Architect.

Mastering PDF Generation in PHP: A Comprehensive Guide to Libraries and Techniques
#

If you have been working in web development for any significant amount of time, you have likely faced the “PDF requirement.” Whether it is generating dynamic invoices, downloadable reports, or shipping labels, creating PDFs programmatically remains a staple requirement for enterprise applications.

As we settle into 2025, the PHP ecosystem offers mature, robust solutions for this task. However, the landscape can be confusing. Should you use a pure PHP library? Should you wrap a binary? Or should you go full headless browser?

In this guide, we will cut through the noise. We will explore the three dominant approaches to generating PDFs in PHP, provide production-ready code, and discuss the performance implications of each.

Why Is This Still Hard?
#

Generating a PDF is fundamentally different from rendering a web page. While browsers are incredibly forgiving of malformed HTML and complex CSS, PDF engines are strict. They often lag behind modern CSS standards (Grid, Flexbox), and they consume significant server resources.

By the end of this article, you will understand:

  1. The Pure PHP Approach: Using Dompdf for simple, dependency-free generation.
  2. The Binary Wrapper Approach: Using Snappy (wkhtmltopdf) for better legacy support.
  3. The Modern Headless Approach: Using Browsershot (Puppeteer) for pixel-perfect rendering.
  4. Performance Optimization: Queues, memory management, and caching.

Prerequisites
#

To follow along, ensure your development environment meets the following criteria:

  • PHP 8.2 or 8.3: We will use typed properties and modern syntax.
  • Composer: For dependency management.
  • Node.js & NPM (Optional): Only required if you plan to use the Browsershot/Puppeteer method.
  • System Libraries: Some PDF tools require libfontconfig or libXrender depending on your OS (Ubuntu/Debian mostly).

Choosing the Right Tool: A Visual Guide
#

Before writing code, you need to select the architecture that fits your use case. Use the flowchart below to determine the best path for your project.

flowchart TD A[Start: PDF Requirement] --> B{Need Pixel-Perfect Modern CSS?} B -- Yes (Grid/Flexbox/JS) --> C[Headless Browser] B -- No --> D{Server Constraints?} C --> E[Browsershot / Puppeteer] D -- Cannot install binaries --> F[Pure PHP] D -- Can install binaries --> G[Binary Wrapper] F --> H[Dompdf / mPDF] G --> I[Snappy / wkhtmltopdf] H --> J[Result: Moderate Speed, Limited CSS] I --> K[Result: Fast, Decent CSS] E --> L[Result: Slowest, Perfect Rendering] style A fill:#f9f,stroke:#333,stroke-width:2px style E fill:#bbf,stroke:#333,stroke-width:2px style H fill:#dfd,stroke:#333,stroke-width:2px style I fill:#dfd,stroke:#333,stroke-width:2px

Library Comparison Matrix
#

Here is a quick breakdown of the technical differences between the major players.

Feature Dompdf Snappy (wkhtmltopdf) Browsershot (Puppeteer)
Engine PHP (HTML to PDF) WebKit (Qt) Chrome (Headless)
CSS Support Basic (No Grid, limited Flex) Moderate (Older WebKit) Excellent (Modern Chrome)
JavaScript No Limited Full Support
Speed Moderate Fast Slow (Heavy resource usage)
Dependencies PHP Extensions (ext-gd) System Binary (wkhtmltopdf) Node.js + Puppeteer
Use Case Simple Invoices, Letters Legacy Reports, Tables Complex Dashboards, Charts

Method 1: The Pure PHP Approach (Dompdf)
#

Dompdf is a classic. It is an HTML-to-PDF converter written entirely in PHP. It is the easiest to set up because it doesn’t require external binaries or Node.js.

Installation
#

First, create a project directory and install Dompdf via Composer.

mkdir php-pdf-demo
cd php-pdf-demo
composer require dompdf/dompdf

The Implementation
#

Here is a robust script that generates a PDF invoice. Note how we handle the configuration to allow remote images (useful for logos).

<?php
// generate_dompdf.php

require 'vendor/autoload.php';

use Dompdf\Dompdf;
use Dompdf\Options;

// 1. Configure Options
// We enable remote access to load images from CDNs or local paths.
$options = new Options();
$options->set('isRemoteEnabled', true);
$options->set('defaultFont', 'Helvetica');
$options->set('chroot', __DIR__); // Security: Restrict file access

// 2. Instantiate Dompdf
$dompdf = new Dompdf($options);

// 3. Prepare HTML Content
// In a real app, this would be a template file (Blade, Twig, or plain PHP)
$html = <<<HTML
<!DOCTYPE html>
<html>
<head>
    <style>
        body { font-family: 'Helvetica', sans-serif; color: #333; }
        .header { text-align: center; margin-bottom: 20px; }
        .invoice-box { border: 1px solid #eee; padding: 20px; box-shadow: 0 0 10px rgba(0,0,0,0.15); }
        table { width: 100%; line-height: inherit; text-align: left; border-collapse: collapse; }
        table td { padding: 5px; vertical-align: top; }
        table tr.heading td { background: #eee; border-bottom: 1px solid #ddd; font-weight: bold; }
        .total { font-weight: bold; text-align: right; margin-top: 20px; }
    </style>
</head>
<body>
    <div class="invoice-box">
        <div class="header">
            <h1>INVOICE #1024</h1>
            <p>Date: 2025-10-15</p>
        </div>
        <table>
            <tr class="heading">
                <td>Item</td>
                <td>Price</td>
            </tr>
            <tr>
                <td>PHP Consultation (Hours)</td>
                <td>$150.00</td>
            </tr>
            <tr>
                <td>Server Optimization</td>
                <td>$300.00</td>
            </tr>
        </table>
        <div class="total">Total: $450.00</div>
    </div>
</body>
</html>
HTML;

// 4. Load HTML
$dompdf->loadHtml($html);

// 5. Setup Paper Size (A4, Letter, etc.)
$dompdf->setPaper('A4', 'portrait');

// 6. Render the HTML as PDF
try {
    $dompdf->render();
} catch (\Exception $e) {
    die("PDF Generation Error: " . $e->getMessage());
}

// 7. Output the generated PDF to Browser
// "Attachment" => false means it opens in the browser, true means force download.
$dompdf->stream("invoice_1024.pdf", ["Attachment" => false]);

Pros: No server configuration needed. Cons: CSS Grid is not supported. Floats can be buggy. Large tables can crash memory.


Method 2: The Binary Wrapper (Snappy / wkhtmltopdf)
#

If you need more speed or slightly better CSS support than Dompdf but don’t want the overhead of Chrome, wkhtmltopdf is the industry veteran. Snappy is the popular PHP wrapper for it.

Note: While wkhtmltopdf is technically deprecated/unmaintained, it remains widely used in production because it is incredibly fast and stable for standard reports.

Installation
#

  1. Install the Binary: You need the wkhtmltopdf binary on your OS.
    • Ubuntu/Debian: sudo apt-get install wkhtmltopdf
    • MacOS: brew install wkhtmltopdf
  2. Install Snappy:
composer require knplabs/knp-snappy

The Implementation
#

<?php
// generate_snappy.php

require 'vendor/autoload.php';

use Knp\Snappy\Pdf;

// 1. Initialize Snappy
// Point this to where your binary is located.
// On Linux usually '/usr/bin/wkhtmltopdf' or '/usr/local/bin/wkhtmltopdf'
$binaryPath = '/usr/local/bin/wkhtmltopdf'; 

// Check if binary exists to avoid vague errors
if (!file_exists($binaryPath)) {
    die("Binary not found at $binaryPath");
}

$snappy = new Pdf($binaryPath);

// 2. Configuration
// Snappy allows passing command line arguments seamlessly
$snappy->setOption('no-outline', true);
$snappy->setOption('page-size', 'LETTER');
$snappy->setOption('margin-top', '10mm');
$snappy->setOption('margin-right', '10mm');
$snappy->setOption('margin-bottom', '10mm');
$snappy->setOption('margin-left', '10mm');

// 3. Generate content
$html = '<h1>Quarterly Report</h1><p>Generated via wkhtmltopdf.</p>';

// 4. Generate and Output
header('Content-Type: application/pdf');
header('Content-Disposition: inline; filename="report.pdf"');

echo $snappy->getOutputFromHtml($html);

Pros: Much faster than Dompdf. Handles page breaks better. Cons: Requires installing a binary on the server. The rendering engine (Qt WebKit) is old and doesn’t support modern CSS features like CSS Variables or Grid.


Method 3: The Modern Way (Browsershot)
#

This is the gold standard for 2025. Browsershot uses a headless version of Google Chrome (via Puppeteer) to take a “screenshot” of your HTML as a PDF. It renders exactly what you see in your browser.

Installation
#

This requires a heavier environment.

  1. Install Node.js & NPM.
  2. Install Puppeteer: npm install puppeteer
  3. Install Browsershot:
composer require spatie/browsershot

The Implementation
#

<?php
// generate_browsershot.php

require 'vendor/autoload.php';

use Spatie\Browsershot\Browsershot;

// Note: Ensure your HTML has full paths to CSS/Images, 
// or use inline styles for simplicity.

$htmlContent = <<<HTML
<div style="display: flex; justify-content: space-between; align-items: center; font-family: sans-serif;">
    <div style="background: #f0f0f0; padding: 20px; border-radius: 8px;">
        <h2 style="color: #2d3748;">Modern Dashboard</h2>
        <p>This uses Flexbox, which Dompdf struggles with.</p>
    </div>
    <div>
        <h1 style="font-size: 3rem; color: #4a5568;">99.9%</h1>
        <span>Uptime</span>
    </div>
</div>
HTML;

try {
    // 1. Setup Browsershot
    // We can chain methods fluently.
    $pdf = Browsershot::html($htmlContent)
        ->setNodeBinary('/usr/bin/node') // Path to Node
        ->setNpmBinary('/usr/bin/npm')   // Path to NPM
        ->format('A4')
        ->margins(10, 10, 10, 10)
        ->showBackground() // Important to print background colors!
        ->pdf();
    
    // 2. Output
    header('Content-Type: application/pdf');
    header('Content-Disposition: inline; filename="dashboard.pdf"');
    echo $pdf;

} catch (\Exception $e) {
    // Common error: Node not found or permissions issues
    echo "Error: " . $e->getMessage();
}

Pros: Perfect rendering. Supports JavaScript, Charts.js, CSS Grid, Webfonts. Cons: Heavy. Slow. Requires Node.js on your production server.


Performance and Pitfalls
#

Generating PDFs is computationally expensive. Here is how to handle it in a production environment.

1. The “120-Second” Timeout Rule
#

Never generate a PDF directly in a user’s HTTP request if it takes more than a few seconds. If you are generating a 50-page report using Browsershot, the browser connection will time out.

Solution: Use a Queue (like RabbitMQ, Redis, or database queues).

  1. User clicks “Download Report”.
  2. PHP dispatches a Job (GeneratePdfJob).
  3. User sees “Processing… we will email you when ready.”
  4. The worker generates the PDF and uploads it to S3.
  5. System sends an email with the signed URL.

2. Memory Leaks
#

Dompdf builds a DOM tree in memory. A 100-page table can easily consume 512MB of RAM.

Solution:

  • Increase PHP memory limit temporarily: ini_set('memory_limit', '512M');
  • If using pure PHP, process large datasets in chunks, though standard PDF libraries struggle with appending pages efficiently without 3rd party merging tools (like libmergepdf).

3. Asset Loading
#

A common error is the Red “X” where an image should be.

  • Dompdf: Ensure isRemoteEnabled is true for URLs. For local files, use absolute server paths (/var/www/html/public/logo.png), not relative URLs (/logo.png).
  • Browsershot: The headless browser needs to be able to resolve the URL. If you are developing locally on localhost:8000, the headless chrome instance needs network access to that port.

Conclusion
#

In 2025, there is no “one size fits all” for PHP PDF generation.

  • Choose Dompdf if you have a simple invoice, restricted server access, and want a pure PHP solution.
  • Choose Snappy if you need speed and have access to install binaries.
  • Choose Browsershot if you need to render charts, complex layouts, or modern CSS designs and have the server resources to spare.

The key to success is keeping your print styles simple. Just because you can use CSS Grid with Browsershot doesn’t mean you should overcomplicate a document destined for a printer.

Next Steps: Try implementing the Browsershot example above, but inject a JavaScript charting library like Chart.js. You will be amazed that the chart renders perfectly in the PDF.

Happy coding!