HTML to PDF
in PHP.
Dompdf, mPDF and TCPDF each implement their own subset of CSS, which is why a page that looks right in the browser rarely looks right in the PDF. Sending the markup to a real browser removes that whole class of bug.
The call
Post your markup with an API key. The PDF comes back in the same response, base64 encoded by default. Nothing is written to disk on our side.
<?php
$ch = curl_init('https://api.html4pdf.com/generate-pdf');
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_TIMEOUT => 60,
CURLOPT_HTTPHEADER => [
'Authorization: Bearer ' . getenv('HTML4PDF_KEY'),
'Content-Type: application/json',
],
CURLOPT_POSTFIELDS => json_encode([
'html' => '<h1>Invoice INV-1042</h1><p>Total: $1,645.40</p>',
'options' => ['format' => 'A4', 'printBackground' => true],
]),
]);
$data = json_decode(curl_exec($ch), true);
curl_close($ch);
file_put_contents('invoice.pdf', base64_decode($data['pdf']));In Laravel
The HTTP client ships with the framework, so a controller that streams an invoice is a handful of lines.
use Illuminate\Support\Facades\Http;
public function download(Invoice $invoice)
{
$pdf = Http::withToken(config('services.html4pdf.key'))
->timeout(60)
->post('https://api.html4pdf.com/generate-pdf', [
'html' => view('invoices.show', compact('invoice'))->render(),
'responseFormat' => 'binary',
])
->throw()
->body();
return response($pdf, 200, [
'Content-Type' => 'application/pdf',
'Content-Disposition' => 'attachment; filename="' . $invoice->number . '.pdf"',
]);
}What comes back
The default response is JSON with the document base64 encoded, the credits you have left, and how long the render took. Send responseFormat as binary instead and the body is the PDF itself, with a Content-Type of application/pdf.
{
"success": true,
"pdf": "JVBERi0xLjQKJeLjz9MKMyAwIG9...",
"creditsRemaining": 9,
"metadata": { "size": 42813, "processingTime": 2841 }
}A 402 means you are out of credits, a 401 means the key is wrong, and a 422 means the markup could not be rendered. Everything else is worth retrying once.
The usual PHP libraries
Worth knowing before you add a dependency, because for a lot of documents one of these is the right answer and costs nothing.
| Library | What it is | The trade-off |
|---|---|---|
| Dompdf | A pure PHP renderer for HTML and CSS 2.1. | No dependencies and easy to install. No flexbox, no grid, no JavaScript, and slow on long documents. |
| mPDF | Pure PHP, with good support for tables, headers, footers and right-to-left text. | Broader CSS than Dompdf, still its own engine rather than a browser. Memory hungry. |
| TCPDF | The long-standing programmatic library. | Mature and everywhere, but you build the document in code rather than in HTML. |
| Browsershot | A Laravel-friendly wrapper that drives headless Chrome through Puppeteer. | Correct rendering, but it needs Node and Chrome installed on the same server as PHP. |
PHP: common questions
What is the best HTML to PDF library for PHP?
For simple documents, Dompdf or mPDF are free and need nothing installed. For anything using flexbox, grid or JavaScript, you need a browser: either Browsershot with Chrome on the server, or an API that runs Chrome for you.
Why does my PDF look nothing like my HTML in Dompdf?
Because Dompdf implements CSS 2.1, not what Chrome implements. Flexbox and grid are ignored, so the layout collapses back to blocks. A browser-based renderer gives you exactly what you see on screen.
Does it work with Laravel?
Yes. Render a Blade view to a string, post it with the built-in HTTP client, and return the body as a PDF response. The example above is a complete controller action.
Do I need shell access or exec()?
No. This is an ordinary HTTPS request, so it works on shared hosting where exec is disabled.
What about WordPress?
The same cURL call works inside a plugin or theme, or use wp_remote_post if you prefer the WordPress HTTP layer.