PHP's HTML-to-PDF landscape splits cleanly: pure-PHP renderers (Dompdf, TCPDF, mPDF) are easy to install but support a 2010-era CSS subset. wkhtmltopdf uses WebKit and handles modern CSS, but needs a system binary and a virtual display. Which to choose depends on how complex your HTML is: Dompdf for invoice templates, wkhtmltopdf for anything with background images or flexbox.
Method 1: Dompdf (pure PHP, CSS 2.1 subset)
Dompdf is the most widely used PHP PDF library. It handles most invoice and report templates well. Modern CSS (flexbox, grid, CSS variables) is not supported.
composer require dompdf/dompdfsetChroot(realpath('./'));
$options->setIsRemoteEnabled(false); // disable for security
$options->setIsHtml5ParserEnabled(true);
$options->setDefaultFont('DejaVu Sans'); // handles Unicode better than Helvetica
$dompdf = new Dompdf($options);
$dompdf->loadHtml($html);
$dompdf->setPaper('A4', 'portrait');
$dompdf->render();
file_put_contents($outPath, $dompdf->output());
}
$html = <<
Invoice #1234
Item Amount
Widget $49.00
HTML;
htmlToPdf($html, './output/invoice.pdf');
echo "Done\n";
Dompdf tips:
- Use
font-family: DejaVu Sansfor best Unicode support. Helvetica and Arial lack many characters. - Disable
setIsRemoteEnabledwhen processing user HTML — it prevents SSRF attacks via external CSS/image URLs. - Dompdf does not support float-based multi-column layouts reliably. Use tables for multi-column content.
Method 2: wkhtmltopdf (WebKit engine, full CSS support)
wkhtmltopdf renders HTML using WebKit — the same engine as older Safari. It handles modern CSS, backgrounds, and @media print rules correctly.
# Ubuntu/Debian
apt install wkhtmltopdf
# Or download binary from https://wkhtmltopdf.org
wkhtmltopdf --version 'A4',
'--margin-top' => '15mm',
'--margin-bottom' => '15mm',
'--margin-left' => '15mm',
'--margin-right' => '15mm',
'--encoding' => 'UTF-8',
'--no-outline' => null,
'--disable-javascript' => null, // security: don't run JS in user HTML
];
$opts = array_merge($defaultOptions, $options);
$flagStr = '';
foreach ($opts as $flag => $value) {
$flagStr .= ' ' . escapeshellarg($flag);
if ($value !== null) {
$flagStr .= ' ' . escapeshellarg($value);
}
}
$cmd = "wkhtmltopdf{$flagStr} " . escapeshellarg($tmpHtml) . ' ' . escapeshellarg($outPath) . ' 2>&1';
$output = shell_exec($cmd);
unlink($tmpHtml);
if (!file_exists($outPath)) {
throw new RuntimeException("wkhtmltopdf failed: {$output}");
}
}
htmlToPdfWk('Hello World
', './output/result.pdf');
echo "Done\n";
Always pass --disable-javascript when processing user-supplied HTML to prevent JS execution. wkhtmltopdf may need --no-sandbox when running as root (inside Docker) — add it only if needed.
Method 3: ChangeThisFile API (Guzzle, no binary installs)
No wkhtmltopdf or Dompdf in your environment? The ChangeThisFile API accepts HTML files and returns PDFs. Source is auto-detected from the filename. Free tier: 1,000 conversions/month, no card required.
composer require guzzlehttp/guzzle 60]);
$response = $client->post('https://changethisfile.com/v1/convert', [
'headers' => ['Authorization' => 'Bearer ' . $apiKey],
'multipart' => [
['name' => 'file', 'contents' => fopen($htmlPath, 'r'), 'filename' => basename($htmlPath)],
['name' => 'target', 'contents' => 'pdf'],
],
]);
file_put_contents($outPath, $response->getBody()->getContents());
}
// Write HTML to temp file, convert, clean up
$html = 'Report
Content here.
';
$tmpPath = tempnam(sys_get_temp_dir(), 'ctf_') . '.html';
file_put_contents($tmpPath, $html);
htmlToPdfApi($tmpPath, './output/report.pdf', 'ctf_sk_your_key_here');
unlink($tmpPath);
echo "Done\n";
When to use each
| Approach | Best for | Tradeoff |
|---|---|---|
| Dompdf | Simple invoices, reports — pure PHP, composer install | CSS 2.1 subset only; no flexbox/grid/CSS variables |
| wkhtmltopdf | Complex CSS, background images, print-media rules | System binary required; no official support for modern sites |
| ChangeThisFile API | Zero local installs, shared hosting, serverless PHP | Network call, 25MB file limit on free tier |
Production tips
- Use @page CSS for margin and size control in Dompdf. @page { size: A4; margin: 20mm; } is more reliable than Dompdf's PHP API for margin control.
- Embed images as base64 in Dompdf. External image URLs in Dompdf are unreliable. Convert images to data URIs: 'data:image/png;base64,' . base64_encode(file_get_contents('logo.png')).
- Pass --load-error-handling ignore to wkhtmltopdf to prevent external resource failures from aborting the conversion.
- Set Dompdf's chroot to your document root. Without chroot, Dompdf can read arbitrary files from the server when processing user HTML with local file:// references.
- Use landscape for wide tables. Pass 'landscape' as the second argument to setPaper() in Dompdf, or --orientation Landscape to wkhtmltopdf.
Dompdf wins for simple invoice templates where you control the HTML. wkhtmltopdf is the right call for anything with real CSS. For zero-dependency environments, the API needs only curl. Get a free key covering 1,000 conversions/month.