dompdf Alternatives: HTML to PDF in Laravel Without a Headless Browser
Generating a PDF in Laravel means choosing which problem you want to own. dompdf makes you write CSS like it's 2011. Snappy chains you to an abandoned binary. Browsershot renders beautifully and quietly turns your PHP server into a Node and Chrome host.
There is a fourth option: render your Blade view exactly as you would for the browser, POST the HTML to an API and get back a real vector PDF. This article covers why the three usual suspects hurt, and what the API route looks like in practice.
Where dompdf falls over
dompdf is a pure PHP renderer, which is exactly why people reach for it: composer require and you are generating PDFs with no binaries and no system dependencies. The cost is the rendering engine. dompdf implements CSS 2.1 with a partial layer of CSS3, and that ceiling shapes everything you build on it.
No flexbox. No grid. Floats behave, mostly, which is why every dompdf invoice template on the internet is built from floats and tables. Web fonts need registering with the font loader rather than just working. And because there is no JavaScript engine, anything your view computes client-side simply never happens.
The practical consequence is a fork in your styles. You have the stylesheet your app actually uses, and a second, degraded one for PDFs, and they drift apart with every design change. The invoice your customer sees on the web and the one attached to their email are two implementations of the same design.
Snappy wraps a dead engine
laravel-snappy hands the work to wkhtmltopdf, which renders with a real browser engine. The catch is which one: a Qt WebKit build frozen around 2012, from a project that is now archived and unmaintained.
That engine predates CSS grid entirely and speaks only the ancient 2009 flexbox syntax. Custom properties do not exist. Security patches stopped when the project did. We have covered the wkhtmltopdf problem in detail before, but for Laravel specifically the summary is: you are compiling a binary onto your server so that a browser from over a decade ago can misinterpret your modern CSS.
Browsershot renders correctly, on your infrastructure
Browsershot fixes the rendering problem properly. It drives headless Chrome through Puppeteer, so flexbox, grid, custom properties and web fonts all work exactly as they do in your browser.
The trade is operational. Your Laravel server now needs Node, Puppeteer and a Chrome binary, kept in version agreement with each other, patched, and given enough memory for Chrome's appetite. That is fine on a box you control. It is a headache on shared hosting, a fight on serverless and a genuine ops burden the moment PDF volume grows. We wrote up the same problem for image rendering in our Browsershot piece; PDFs inherit all of it.
Render the Blade view, POST it, get a PDF
The HTML to PDF API route keeps Chrome rendering and removes Chrome from your server. Render your view to a string, send it with format: "pdf", get back a URL to the finished document:
use Illuminate\Support\Facades\Http;
$response = Http::withHeaders([
'X-API-Key' => config('services.html2img.key'),
])->post('https://app.html2img.com/api/html', [
'html' => view('invoices.show', ['invoice' => $invoice])->render(),
'format' => 'pdf',
]);
$url = $response->json('url');
// https://i.html2img.com/image-....pdf The response is the same JSON envelope as an image render, the file is a real vector PDF, and it costs one credit like any other render. Text stays selectable and searchable, web fonts are embedded, and long content paginates across A4 pages on its own. Because the render happens in current Chrome, the stylesheet is the one your app already uses. No fork.
Or use the Laravel package
From version 1.1, the Laravel package exposes the same option as an enum:
composer require html2img/html2img-laravel use Html2img\Enum\Format;
use Html2img\Laravel\Facades\Html2img;
use Html2img\Request\HtmlRequest;
$response = Html2img::html(new HtmlRequest(
html: view('invoices.show', ['invoice' => $invoice])->render(),
format: Format::Pdf,
));
$path = Html2img::store($response, "invoices/{$invoice->number}.pdf"); Html2img::store() drops the finished PDF onto any filesystem disk your app has configured, S3 included, which is usually where an invoice needs to end up anyway.
Attach it to a Mailable
The most common destination for a Laravel PDF is an email. With the file stored on a disk, attachment is one line in the Mailable:
use Illuminate\Mail\Mailables\Attachment;
public function attachments(): array
{
return [
Attachment::fromStorage("invoices/{$this->invoice->number}.pdf")
->withMime('application/pdf'),
];
} If you would rather show the invoice inline in the email body instead of attaching a document, render it as a PNG with the same markup and skip the format parameter. Same view, two outputs.
Know what the PDF is
Worth being clear-eyed about the output so your markup plays to it. Pages are A4 portrait and content reflows to the page width, so width, height, dpi, fullpage and selector do nothing in PDF mode. The document renders with your normal screen CSS rather than @media print, which is the point: the PDF looks exactly like the image render would. Write the view to flow like a document rather than to fixed pixel dimensions and pagination takes care of itself. The format parameter docs cover the full behaviour.
For big documents, add a webhook_url and we will POST you the finished URL rather than holding your request open, which pairs naturally with a queued job.
Need invoices, certificates or reports rendered from the Blade views you already have, without a browser on your server? Browse the templates gallery or read the docs to get started.