Convert HTML to PDF in PHP

24 June 2026
This post thumbnail

The fastest way to convert HTML to PDF in PHP in 2026 is Dompdf for standard server-rendered templates, mPDF when you need Unicode or right-to-left languages, TCPDF when you need digital signatures or PDF/A archival, and a hosted Chrome engine when your HTML depends on JavaScript or Browsershot's Node and Chromium runtime is too heavy for your hosting. Pick one path, paste the code below, and ship.

Key Takeaways

  • Dompdf is the most-downloaded PHP HTML-to-PDF library (about 134 million Composer installs) and the right starting point when your HTML is CSS-only and your text is mostly ASCII.
  • mPDF is the answer for multilingual documents: Unicode, right-to-left languages, CJK fonts, and automatic table-of-contents output without writing the Unicode handling yourself.
  • No pure-PHP library executes JavaScript. If your HTML renders charts client-side or uses modern CSS like grid and flex, you need a browser-backed path (Browsershot) or a hosted Chrome engine.
  • Snappy and barryvdh/laravel-snappy wrap the archived wkhtmltopdf binary (last release 2020, two open critical CVEs). Plan a migration off them, either to Browsershot or to a hosted engine that maintains a sandboxed wkhtmltopdf renderer.
  • For Laravel, use barryvdh/laravel-dompdf for simple invoices and spatie/laravel-pdf (Browsershot under the hood) when the template needs modern CSS or JavaScript. Both render Blade templates directly.

PHP has had four HTML-to-PDF libraries for the better part of a decade, and the right one for your project still depends on three questions most tutorials never ask. Does your HTML need JavaScript? Are you on hosting that can run Node and Chromium, or stuck on a managed PHP plan? And are you still on Snappy, which wraps a binary that hasn't shipped a release since 2020? This guide walks through the four paths a PHP team can take, the runnable code for each, the Composer footprint each one drags into your project, and a clear marker for when pure-PHP rendering stops being worth the limits. By the end you'll have a Laravel Blade-to-PDF endpoint, a Symfony note, and an honest read on when to stop owning the renderer.

How to convert HTML to PDF in PHP (the short answer)

To convert HTML to PDF in PHP, you composer require dompdf/dompdf, load your HTML into a Dompdf\Dompdf instance with loadHtml, call render, and stream the bytes back with output. Five lines of code and you have a PDF. For anything beyond ASCII text and CSS 2.1, you'll want a different library or a hosted engine. Here's the full landscape.

Dompdf vs mPDF vs TCPDF

Path License Engine type JS Modern CSS Composer / binary footprint Best for
Dompdf LGPL 2.1 Pure PHP No CSS 2.1 + bits of CSS 3 Composer only Most server-rendered, CSS-styled templates
mPDF GPL 2.0 Pure PHP No CSS 2.1 + better text layout Composer only Unicode, RTL, CJK, multilingual invoices
TCPDF LGPL 3.0 Pure PHP No Limited HTML support Composer only Digital signatures, barcodes, PDF/A, fillable forms
Snappy (KnpLabs/snappy) MIT Wraps archived wkhtmltopdf Limited CSS 2.1 + some CSS 3 Composer + wkhtmltopdf binary Legacy projects; not recommended for new builds
Browsershot / Spatie laravel-pdf MIT Headless Chrome (via Node + Puppeteer) Yes Everything Chrome supports Composer + Node + Chromium on the server Modern CSS, JS rendering, when you control the server
Hosted Chrome engine (Transformy and similar) SaaS Full Chromium Yes Everything Chrome supports One HTTP call, no local browser When you can't (or don't want to) ship Node + Chromium

Two questions reduce the decision. Does your HTML need JavaScript to look right? And does your hosting allow you to install Node and Chromium? Answer those honestly and the path picks itself.

Choose the right PHP HTML-to-PDF library

Most teams overthink this. Here's the decision tree I use:

  1. If the HTML is fully server-rendered, CSS-only, and your text is mostly ASCII, use Dompdf. Free, pure PHP, no binaries, runs on shared hosting.
  2. If you need Unicode, right-to-left languages (Arabic, Hebrew), or CJK (Chinese, Japanese, Korean), reach for mPDF. It handles fonts and text shaping that Dompdf will mangle.
  3. If you need digital signatures, 1D or 2D barcodes, fillable forms, or PDF/A archival, use TCPDF. Its HTML renderer is the weakest of the three, but its programmatic PDF API is the most complete.
  4. If your HTML depends on JavaScript or modern CSS (grid, flex, container queries, webfonts from a CDN), you need a browser. Browsershot if you control the server, or a hosted Chrome engine if you don't.
  5. If you're on shared hosting and can't install Node + Chromium, your choices collapse to pure-PHP (Dompdf or mPDF) or a hosted API. Browsershot is off the table.
  6. If you're on Snappy and it just stopped working, that's the archived wkhtmltopdf binary. Migration paths are below.

Convert HTML to PDF in PHP with Dompdf

Dompdf is the most-downloaded PHP HTML-to-PDF library on Packagist (about 134 million installs, around 10,800 GitHub stars at the time of writing). It's pure PHP, has no binary dependencies, and runs on every PHP host that supports Composer, including shared hosting where you can't install system packages. CSS coverage stops at CSS 2.1 plus a few CSS 3 properties. Good enough for invoices, statements, and the boring 80% of business documents.

Composer dependency

composer require dompdf/dompdf

Minimal converter (HTML string to PDF file)

<?php

require __DIR__ . '/vendor/autoload.php';

use Dompdf\Dompdf;
use Dompdf\Options;

$options = new Options();
$options->set('defaultFont', 'sans-serif');
$options->set('isRemoteEnabled', false); // Default. Only enable when you control the URLs.

$dompdf = new Dompdf($options);

$html = <<<'HTML'
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <style>
    body { font-family: sans-serif; padding: 24px; }
    h1 { color: #1f6feb; }
    table { width: 100%; border-collapse: collapse; }
    td, th { border: 1px solid #ddd; padding: 8px; }
    tr { page-break-inside: avoid; }
  </style>
</head>
<body>
  <h1>Invoice #1041</h1>
  <table>
    <tr><th>Item</th><th>Total</th></tr>
    <tr><td>Hosting (Jun)</td><td>$49.00</td></tr>
  </table>
</body>
</html>
HTML;

$dompdf->loadHtml($html);
$dompdf->setPaper('A4', 'portrait');
$dompdf->render();

file_put_contents(__DIR__ . '/invoice.pdf', $dompdf->output());

Drop it in a file, run php script.php, and you have invoice.pdf on disk. Three settings in that snippet are worth defending.

isRemoteEnabled defaults to false because Dompdf will follow <img src> and <link href> URLs at render time. Flipping it to true against user-supplied HTML opens an SSRF (Server-Side Request Forgery) vector where someone can probe your internal network through the PDF renderer. Only enable it for HTML you control, and consider running Dompdf in a separate worker with no internal network access.

setPaper('A4', 'portrait') is explicit because Dompdf's default is US Letter. If your customers are outside the US, set the size in code. The other common values are Letter, Legal, and the rest of the ISO A series.

page-break-inside: avoid on the <tr> element is the single most common Dompdf support question, dressed up as a layout bug. Tables split across pages unless you tell them not to. The CSS Paged Media spec has the full property list, and Dompdf respects most of the common ones.

Gotcha: CSS support stops at CSS 2.1

No flex, no grid, no container queries. Design your PDF templates around floats and tables, and test rendered output rather than browser preview. If the layout needs flex or grid, you've outgrown Dompdf.

Gotcha: Composer dependency conflicts

Dompdf pulls in phenx/php-font-lib, sabberworm/php-css-parser, and masterminds/html5. Version pins on those packages can conflict with Symfony, Laravel, or large internal codebases that depend on different versions. The fix is usually composer update --with-all-dependencies, sometimes a forked compatibility branch. Pin Dompdf to a specific minor version in production so the conflict surface stays predictable.

Gotcha: webfonts and the cost of fonts

Dompdf's default fonts are Helvetica, Times, and Courier. Anything else requires registration through the font loader and a font file you ship with the app. Pulling fonts from Google Fonts at render time is not an option, even if you flip isRemoteEnabled. Self-host the font file in storage/fonts/ (or wherever) and reference it in your CSS by the family name you register.

Convert HTML to PDF in PHP with mPDF (when languages matter)

mPDF is the answer when Dompdf isn't good enough at text. It handles UTF-8 out of the box, supports right-to-left languages (Arabic, Hebrew, Persian) without configuration, includes CJK font shaping, and generates automatic tables of contents. CSS coverage sits roughly in line with Dompdf, but the text-layout engine is better in every dimension that matters for multilingual content.

Aisha runs the back office for a SaaS that invoices customers in 14 countries. She built the first version on Dompdf in 2024 and shipped invoices to English-speaking customers without trouble. Then her first Saudi client got an invoice and the Arabic text rendered left-to-right with the letters disconnected. She tried three Dompdf font configurations, lost two days, and finally swapped to mPDF in an afternoon. The Arabic rendered correctly on the first try. Her conclusion was the right one: the library and the document have to match the language.

composer require mpdf/mpdf
<?php
require __DIR__ . '/vendor/autoload.php';

$mpdf = new \Mpdf\Mpdf([
    'mode' => 'utf-8',
    'format' => 'A4',
    'tempDir' => __DIR__ . '/mpdf-tmp',
]);

$html = '<h1>الفاتورة رقم ١٠٤١</h1><p>الإجمالي: 49.00 ﷼</p>';
$mpdf->WriteHTML($html);
$mpdf->Output(__DIR__ . '/invoice-ar.pdf', \Mpdf\Output\Destination::FILE);

One gotcha: mPDF generates a lot of small files in its temp directory while it works. On a server with a strict disk quota or a tight tmpfs, it'll fail mid-render. Point tempDir at a directory you control, and clean it up periodically (a cron job that deletes files older than 24 hours covers most cases).

Convert HTML to PDF in PHP with TCPDF (when you need PDF features, not HTML fidelity)

TCPDF deserves a place on the list, but with a caveat: its HTML rendering is the weakest of the three majors. Reach for TCPDF when you need its programmatic features, not its HTML support. Those features include digital signatures, 1D and 2D barcodes (including QR codes), fillable PDF forms, and PDF/A archival output, all of which are first-class citizens in TCPDF and absent or weak in Dompdf and mPDF.

The project itself is in support-only mode (no new features, security fixes only). The maintainer is building tc-lib-pdf as a successor, but it's still in active development and not a drop-in replacement.

composer require tecnickcom/tcpdf
<?php
require __DIR__ . '/vendor/autoload.php';

$pdf = new TCPDF('P', 'mm', 'A4', true, 'UTF-8');
$pdf->SetCreator('Acme Billing');
$pdf->AddPage();
$pdf->writeHTML('<h1>Invoice #1041</h1><p>Total: $49.00</p>', true, false, true, false, '');
$pdf->Output(__DIR__ . '/invoice.pdf', 'F');

If your HTML is more than a heading and a paragraph, the rendering will start to deviate from what your browser shows. That's the point at which most teams either move the HTML rendering to Dompdf and the post-processing to TCPDF, or skip TCPDF entirely.

HTML to PDF in Laravel (Blade template to PDF endpoint)

Two Laravel paths matter in 2026. Each has a clean use case.

Option A: barryvdh/laravel-dompdf (the most common Laravel path)

This is the canonical Laravel HTML-to-PDF setup. It wraps Dompdf in a Laravel-friendly facade and renders Blade templates directly.

composer require barryvdh/laravel-dompdf
<?php
// app/Http/Controllers/InvoiceController.php

namespace App\Http\Controllers;

use App\Models\Invoice;
use Barryvdh\DomPDF\Facade\Pdf;
use Illuminate\Http\Request;

class InvoiceController extends Controller
{
    public function download(Invoice $invoice)
    {
        $pdf = Pdf::loadView('invoices.show', ['invoice' => $invoice])
            ->setPaper('A4', 'portrait');

        return $pdf->download("invoice-{$invoice->id}.pdf");
    }
}

Your Blade template at resources/views/invoices/show.blade.php is a normal Blade file. Whatever HTML and CSS your customer-facing UI already renders, you reuse here, and the same source of truth produces both the web view and the PDF.

Option B: spatie/laravel-pdf (the modern Browsershot wrapper)

When your Blade template needs JavaScript, modern CSS, or fonts you can't ship locally, Spatie's laravel-pdf package wraps Browsershot, which wraps Puppeteer, which drives a real Chromium browser. The output is pixel-accurate. The cost is operational.

composer require spatie/laravel-pdf
# Requires Node, npm, and a working Chromium install on the same server.
npm install puppeteer
<?php
use Spatie\LaravelPdf\Facades\Pdf;

return Pdf::view('invoices.show', ['invoice' => $invoice])
    ->format('a4')
    ->name("invoice-{$invoice->id}.pdf")
    ->download();

Reach for this when the simple Dompdf path falls short and your hosting can run Node and Chromium. Skip it if you're on shared hosting, on a small managed VPS, or on a PaaS that doesn't let you install system binaries. The Spatie team is honest about the tradeoffs on their alternatives page, which is worth reading before you commit.

Production gotcha: render on a queue, not on the request thread

PDF rendering is CPU-bound and holds the whole document in memory while it works. A Download PDF button is fine. A "Email this report to 5,000 customers" job will eat your PHP-FPM workers.

<?php
// app/Jobs/RenderInvoicePdf.php
RenderInvoicePdf::dispatch($invoice)->onQueue('pdfs');

Push the render into a queued job, store the bytes to S3 (or local disk), and either email a download link or hit a webhook when it's ready. Your web tier stays responsive, your queue worker pool absorbs the spike, and you can scale them independently.

HTML to PDF in Symfony (Snappy, Browsershot, and the modern path)

Symfony teams have historically reached for KnpSnappyBundle, which wraps Snappy, which wraps wkhtmltopdf. That binary has been archived since 2023 with no plan to revive it. For new Symfony projects in 2026, the pragmatic path is one of: install Dompdf directly as a service for simple invoices, install Browsershot when you control the server and need modern CSS, or call a hosted API for anything you'd rather not maintain. The Symfony service container makes any of the three a one-line wire-up.

When PHP HTML-to-PDF libraries hit a wall

Every PHP team eventually meets one of four walls. Each is where the library question stops being "which one?" and starts being "call something else?"

Wall 1: JavaScript-rendered HTML

No pure-PHP library executes JavaScript. If your invoice template paints with Vue or your dashboard renders charts with Chart.js client-side, Dompdf and mPDF will quietly emit empty rectangles where the dynamic content should be. Three honest options when you hit this wall: run Browsershot yourself (if you control the server), self-host a Gotenberg or Browserless container (still your infrastructure), or call a hosted Chrome engine and skip the browser entirely.

Wall 2: modern CSS

Dompdf covers CSS 2.1 with bits of CSS 3. mPDF is similar with better text. Bleeding-edge features (grid, flex edge cases, container queries) fail silently or render wrong, with no error to grep for. Chrome rendering matches Chrome because it is Chrome. If a designer ships a layout that depends on a CSS feature shipped in the last 18 months, pure-PHP libraries can't hold the line.

Wall 3: shared hosting and the Node + Chromium tax

A real constraint, especially for indie devs and agencies. Browsershot needs Node and a working Chromium on the same server as your PHP. On shared hosting and most managed PHP plans, you can't install either. On small managed VPS plans, Chromium can eat more RAM than your app does. When the operational cost of running a browser locally outweighs the cost of a hosted API call, the math flips. For most PHP teams below mid-size, that math flips immediately.

Wall 4: Snappy and the wkhtmltopdf archive

Marcus runs a Laravel shop with three apps still on barryvdh/laravel-snappy. The renders kept working through 2025 because the wkhtmltopdf binary doesn't change, but the security audit flagged the two open critical CVEs in 2026 and gave him 60 days to migrate. Three options were on his table. Port to Browsershot (and put Chromium on every server). Port to Dompdf (and accept the CSS downgrade for two templates that needed flex). Or call a hosted engine that keeps a maintained, sandboxed wkhtmltopdf renderer behind the same API as headless Chrome. He picked the third because it required the fewest code changes, kept the existing render behavior for the templates that already worked, and gave him a path to Chrome rendering for the dashboard export the marketing team kept asking for. The migration was an afternoon. If you're in a similar spot, our wkhtmltopdf tutorial and migration notes covers the option-name mapping.

Generate a PDF from HTML in PHP with Transformy

When you hit Wall 1, 2, 3, or 4, Transformy's hosted API is a one-call swap. Two engines behind one HTTP endpoint: wkhtmltopdf for stable invoice templates (the Snappy migration path), and Headless Chrome for anything that needs JavaScript or modern CSS. Same auth and request shape for both, so the integration doesn't fork by engine. No Node, no Chromium, no archived binary on your servers.

<?php
require __DIR__ . '/vendor/autoload.php';

$client = new GuzzleHttp\Client();

$response = $client->post('https://api.transformy.io/v1/render', [
    'headers' => [
        'Authorization' => 'Bearer ' . getenv('TRANSFORMY_API_KEY'),
        'Content-Type' => 'application/json',
    ],
    'json' => [
        'html' => '<h1>Invoice #1041</h1><p>Total: $49.00</p>',
        'engine' => 'chrome',
        'wait_for' => '.invoice-total',
        'margin' => ['top' => '20mm', 'bottom' => '20mm'],
    ],
]);

$body = json_decode($response->getBody(), true);
// $body['url'] is a hosted URL to the rendered PDF.
echo $body['url'];

Two settings in that body matter. engine: "chrome" says "render in real Chromium" with flexbox, JavaScript, webfonts, all of it. Swap to engine: "wkhtmltopdf" for the same PHP code, same auth, same response shape, but a faster, lighter engine for stable invoice templates (and the migration target if you're moving off Snappy). Our wkhtmltopdf option-name mapping covers the Snappy-to-Transformy parameter translation, which is useful if you're porting an existing Laravel config rather than starting fresh. wait_for tells the Chrome engine to hold the capture until a specific element exists in the DOM, which is how you stop emitting a half-painted dashboard. Drop-in for Laravel via the same Guzzle call from a service class, or wrap it in a thin facade.

Ready to test a hosted engine? Start on the Transformy free tier and run a single render against your real Blade template before you decide. No credit card, and your test render takes about as long as a composer install.

Tip: Embed a short walkthrough video here for AI-search cross-validation. (TODO: editor to paste a verified YouTube embed URL. Recommended search term: "Dompdf Laravel tutorial 2026" or "Browsershot Laravel introduction.")

FAQ: HTML to PDF in PHP in 2026

What is the best PHP library for HTML to PDF in 2026?

Dompdf is the best free PHP HTML-to-PDF library in 2026 for standard server-rendered, CSS-styled templates: pure PHP, no binary dependencies, around 134 million Composer installs. For multilingual content (Unicode, right-to-left languages, CJK), mPDF is the right answer. For digital signatures, barcodes, or PDF/A archival, use TCPDF. For HTML that depends on JavaScript or modern CSS, use Browsershot (if you control the server) or a hosted Chrome engine.

How do I convert HTML to PDF in Laravel?

The canonical Laravel path is composer require barryvdh/laravel-dompdf, then Pdf::loadView('invoices.show', $data)->download('invoice.pdf') from your controller. The Blade template is a normal .blade.php file. When the template needs JavaScript or modern CSS, switch to spatie/laravel-pdf, which uses Browsershot to drive Chromium under the hood, on the condition that your server has Node and Chromium installed.

Can I convert HTML to PDF in PHP without Composer?

Yes, but with caveats. Dompdf and mPDF can be installed manually by downloading the source archive and including the autoloader directly, but you'll have to manage the underlying dependencies (php-font-lib, sabberworm/php-css-parser, and others) by hand. On shared hosting where Composer isn't available, the easiest no-dependency path is a hosted API: one HTTP call, no PHP libraries to install at all.

Does Dompdf support modern CSS like flexbox and grid?

No. Dompdf covers CSS 2.1 with a few CSS 3 properties (border-radius, transform, some font properties). Flexbox, grid, container queries, and most modern CSS layout features will be ignored or render incorrectly. Design Dompdf templates around floats and tables, or use a browser-backed renderer (Browsershot, hosted Chrome engine) when modern CSS is non-negotiable.

Is Snappy still safe to use in 2026?

Not for new projects. Snappy (KnpLabs/snappy and barryvdh/laravel-snappy) wraps the wkhtmltopdf binary, which has been archived since 2023, with no new releases since 2020 and two open critical CVEs (CVE-2022-35583 SSRF at CVSS 9.8, CVE-2020-21365 path traversal at CVSS 7.5). The library itself is fine; the binary is not. If you're on Snappy in production, plan a migration to Browsershot, Dompdf, or a hosted engine that keeps a maintained wkhtmltopdf renderer.

How do I generate a PDF invoice from HTML in PHP?

Render your invoice HTML from a Blade or Twig template, hand the string to Dompdf's loadHtml, call render, and return the bytes with output. For Laravel, use barryvdh/laravel-dompdf and Pdf::loadView('invoices.show', $data)->download(...). For batch invoice runs (end-of-month billing), push the render onto a queue and email the customer a link to the finished PDF; rendering on the request thread will starve your web workers under load.

Wrapping up: pick the smallest PHP tool that ships the PDF

Most PHP teams ship HTML to PDF on the wrong library because they pick by habit (or by the first Stack Overflow answer) instead of by document. The 2026 answer is simpler than the archive suggests:

  • For server-rendered, CSS-styled documents in any framework, use Dompdf. Free, pure PHP, runs anywhere.
  • For multilingual content (Unicode, right-to-left, CJK), use mPDF.
  • For digital signatures, PDF/A, or fillable forms, use TCPDF.
  • For HTML that depends on JavaScript, use Browsershot (with Node and Chromium on your server) or a hosted Chrome engine.
  • If you're on Snappy, plan a migration before the next security audit forces one.

You can build the whole thing yourself in PHP, and for most invoice and statement workloads, you should. The honest test is whether HTML-to-PDF rendering is core to what your team ships or a feature you'd rather forget about between releases. If it's the latter, render your first PDF on the Transformy free tier. Point it at the same Blade or Twig template your app already produces, with the engine that fits the page. We'll be here when you outgrow Dompdf, and we'll tell you honestly when you haven't.