The journal · 19 July 2026 · 9 min read
HTML to PDF in PHP: the libraries that actually work in 2026
Most PHP HTML to PDF tutorials still hand you dompdf or a wrapper around an abandoned binary. Here is the current map for PHP and Laravel: which library renders modern CSS, which is a security risk, and the code for each.
If you are generating PDFs from a PHP or Laravel app, the choice is not really "which library" but "which rendering engine, and do I want to own it." There are three families: pure PHP renderers that parse HTML themselves (dompdf, mPDF, TCPDF), wrappers around an external binary (Snappy over wkhtmltopdf), and drivers for a real browser (Spatie Browsershot over headless Chrome, or a hosted render API). They differ enormously in how much modern CSS survives and how much operational weight you sign up for. Here is the current map, with working code and the failure mode of each.
The short version
For simple, controlled layouts (a plain invoice, a table, a letter) dompdf or mPDF are fine and install in one Composer command. The moment your template uses flexbox, CSS grid, modern web fonts or any JavaScript, those pure PHP parsers fall apart, and you need something that renders in an actual browser engine: Browsershot if you are willing to run headless Chrome yourself, or a rendering API if you would rather not. Snappy over wkhtmltopdf, still the top Google result for this query, wraps a binary that was archived in January 2023 and carries an unpatched CVE, so it should be off your shortlist for anything new.
dompdf: the default, and its ceiling
dompdf is the library most PHP tutorials reach for, and Laravel developers usually meet it through the barryvdh/laravel-dompdf wrapper. It is pure PHP, so there is nothing to install at the system level, and for a straightforward document it just works. The Laravel HTML to PDF page compares these same options specifically for Blade views if that is your stack.
// Laravel with barryvdh/laravel-dompdf
use Barryvdh\DomPDF\Facade\Pdf;
$pdf = Pdf::loadView('invoices.show', ['invoice' => $invoice])
->setPaper('a4');
return $pdf->download("invoice-{$invoice->number}.pdf");
The ceiling arrives fast. dompdf implements its own HTML and CSS engine, and that engine is roughly a subset of CSS 2.1. Flexbox and grid are not supported, so a layout built with them collapses. Web font handling is fiddly, external resources need explicit configuration, and anything that depends on JavaScript is simply ignored, because there is no browser to run it. If your template was designed for the web and looks right in Chrome, dompdf will very often not match it. It is the right tool only when you build the template specifically for dompdf and keep it simple.
mPDF and TCPDF: more features, same category
mPDF supports more CSS than dompdf, has decent Unicode and right to left handling, and is a reasonable pick for reports, certificates and multilingual documents. TCPDF is older, powerful for programmatic PDF construction, and painful for HTML that was not written for it. Both are pure PHP and both share dompdf's fundamental limit: they parse HTML and CSS themselves rather than rendering it in a browser, so modern layout and JavaScript are out of reach. If dompdf is close but you need better CSS or non Latin scripts, mPDF is the upgrade inside this family. If you need the PDF to match a real browser, no library in this family will get you there.
Snappy and wkhtmltopdf: the popular answer to avoid in 2026
Snappy (knplabs/knp-snappy, or barryvdh/laravel-snappy in Laravel) is a PHP wrapper that shells out to the wkhtmltopdf binary. For years it was the standard way to get browser-like output from PHP, because wkhtmltopdf rendered with a real WebKit engine and handled far more CSS than dompdf.
That recommendation is out of date. The wkhtmltopdf project was archived in January 2023 and is no longer maintained. It ships an old, frozen WebKit that predates modern CSS, so flexbox and grid render inconsistently, and, more seriously, it carries CVE-2022-35583, a server side request forgery flaw rated 9.8 out of 10 that will not be patched because nobody is patching wkhtmltopdf anymore. Building a new PHP PDF pipeline on Snappy in 2026 means installing an abandoned binary with a critical unpatched vulnerability on your servers. The full case is in our wkhtmltopdf alternative page. Treat Snappy as legacy: keep it running if you must, but do not start here.
Spatie Browsershot: real Chrome, if you run the browser
When you need the PDF to match a modern browser, the honest options both use one: a real headless Chrome. In the Laravel and PHP world, the popular way to drive it yourself is Spatie's Browsershot, which controls headless Chromium through Puppeteer under the hood.
use Spatie\Browsershot\Browsershot;
Browsershot::html($renderedHtml)
->format('A4')
->showBackground() // Chrome drops backgrounds in print by default
->waitUntilNetworkIdle() // let late content finish loading
->save($path);
The output is excellent, because it is Chromium doing the rendering: your flexbox, grid, web fonts and JavaScript all work. The cost is that you now run a headless browser in production. That means installing Node and Chrome on every server and container that generates a PDF, keeping them patched, and absorbing the operational reality that headless Chrome leaks memory, spikes CPU, and crashes under concurrency. On shared hosting it usually is not even possible, because you cannot install the browser. Browsershot is the right tool when you control your infrastructure, want browser fidelity, and are willing to own the fleet. That last part is the whole decision, and we cover the true cost of it in the library versus API comparison.
The showBackground() call above is worth calling out. Chromium strips background colors and images in print mode unless you ask it not to, and forgetting this is the single most common reason a browser-rendered PDF comes out looking washed out compared to the screen. It bites Browsershot, Puppeteer and Playwright users equally.
A rendering API: the same Chrome, without the fleet
The other way to get real browser output is to call a service that runs the browser for you. From PHP that is one HTTP request, no Node, no Chrome, no memory management, and it works fine on shared hosting because nothing renders on your box.
// Any PHP app: one request, no browser to install
$response = Http::withToken(config('services.sitepdf.key'))
->asForm()
->post('https://api.sitepdf.com/v1/render', [
'html' => $renderedHtml,
'format' => 'A4',
'archive' => true, // keep a timestamped copy of this document
]);
$pdfUrl = $response->json('pdf_url');
The tradeoff is the mirror image of Browsershot. You give up direct control of the browser and take on a network dependency and a per render cost, and in exchange you delete an entire category of production problems: no browser to patch, no fleet to scale, no 3am page when Chrome eats the memory on the invoice server. For a finance or billing feature where the PDF has to be right every time and downtime is expensive, that is usually the better trade. Sitepdf renders your HTML or a live URL in managed Chromium and, with archive=true, keeps a timestamped snapshot of every document it produces, which matters when someone disputes an invoice months later. If you are building document generation from Blade templates, the PDF generator API page covers that workflow, and the invoice from HTML guide walks a concrete example.
How to choose, quickly
| Your situation | Use |
|---|---|
| Simple layout you control, no modern CSS or JS | dompdf (or mPDF for better CSS and Unicode) |
| Multilingual or right to left documents, still simple layout | mPDF |
| Template uses flexbox, grid, web fonts or JavaScript, and you run your own servers | Spatie Browsershot (real Chrome, you own the fleet) |
| Same modern template, but shared hosting or no appetite to run a browser | A rendering API |
| You need a dated record of every document, not just the file | An API with archiving |
| Any new project currently reaching for Snappy or wkhtmltopdf | Stop; pick Browsershot or an API instead |
The deciding question is almost never "which PHP library is best" in the abstract. It is "does my template need a real browser, and if it does, do I want to run that browser myself." Get that right and the library choice falls out of it. Once your app is reliably turning HTML into finished documents, the next problem is usually what to do with the data inside them; if those documents are financial reports, a tool that assembles a board-ready set of financial statements from your bookkeeping export can pick up where the rendering leaves off.
For the neighboring language guides, see HTML to PDF in Python and HTML to PDF in JavaScript, and for the full field of hosted options with pricing, the seven tool comparison.
Written by the team building Sitepdf, an HTML to PDF API that archives every page it renders. The in-browser converter is free to try; early access locks the launch pricing.