
There always arises a need for converting content from one file format to another. Some need to convert text into HTML, and some need to convert HTML content into an image or a document. The main reason is that the target format is best suited for the medium where the content will be consumed — an email, a printed hard copy, or a web browser.
Plain text is best suited for transactional emails, since the chance of content getting mangled in transit is much lower than with HTML-formatted emails. A PDF or Word document is the right choice for a printed hard copy, and HTML is obviously best for showing content in browsers. PDF is the right choice for documents such as bills or invoices that need to be downloaded. When you build a backend application — a CRM, an invoicing system, a WooCommerce store that emails order receipts — you need documents created dynamically, on the fly, and delivered as PDF: either as email attachments or as downloads.
Here, we are going to look at converting HTML 2 PDF using PHP. This article is not going to explain the internal logic of how an HTML file becomes a PDF file — that is a rendering-engine subject of its own. Instead, we cover the free, open-source PHP libraries that do the conversion for you, which ones are still worth using, and which ones you should retire.
A note on this article’s age: we first published this guide well over a decade ago, and it has ranked for PHP-to-PDF searches ever since. The PHP ecosystem has moved on, so we have thoroughly revised it — keeping what still works, and being honest about what doesn’t. If you followed the old version of this tutorial, read the HTML2FPDF section below before touching production code.
Which PHP PDF Library Should You Use in 2026?
The short answer: use Dompdf to convert HTML to PDF in PHP — it is Composer-installable, actively maintained (v3.1.x as of 2026), and handles CSS 2.1 plus enough CSS3 for invoices, receipts, and reports. Use FPDF when you are drawing a PDF programmatically without HTML and want zero dependencies. Use mPDF when you need strong UTF-8 and non-Latin script support — it is the maintained descendant of the old HTML2FPDF library this article originally taught. And when the PDF must look pixel-identical to a CSS-heavy page, skip PHP rendering engines entirely and drive headless Chrome.
In our client work — mostly WooCommerce, PrestaShop, and custom PHP applications for US and UK businesses — Dompdf covers roughly eight out of ten PDF requirements. The rest of this article walks through each option with working code.
FPDF: The PDF Generator
The first and the main base for PDF generation in PHP is the FPDF library. FPDF is a pure PHP class to generate PDF files on the fly, with no external dependencies and no extensions beyond zlib and GD. It has been around for over two decades, and — unusually for a library that old — it is still maintained: version 1.86 supports PHP 8, and the official Composer package is setasign/fpdf.
composer require setasign/fpdf
Let us start the PDF generation with a simple Hello World:
<?php
require __DIR__ . '/vendor/autoload.php';
$pdf = new FPDF();
$pdf->AddPage();
$pdf->SetFont('Arial', 'B', 16);
$pdf->Cell(40, 10, 'Hello World!');
$pdf->Output();
We create an FPDF object using the default constructor FPDF(). This constructor accepts three values: page orientation (portrait or landscape), the measure unit, and the page size (A4, A5, Letter, and so on). By default, pages are A4 portrait and the measuring unit is the millimeter. It could have been specified explicitly with:
$pdf = new FPDF('P', 'mm', 'A4');
It is possible to use landscape (L), other page formats (such as Letter and Legal), and other measure units (pt, cm, in).
Then we add a page to the PDF document with AddPage(). The origin is at the upper-left corner, and the current position is placed 1 cm from the borders by default; the margins can be changed with SetMargins() — call it before AddPage() if you want it to apply from the first page.
To print text, we first select a font with SetFont(). We use the Cell() function to output text. A cell is a rectangular area, possibly framed, which contains some text. It is output at the current position. We specify its dimensions, its text (centered or aligned), whether borders should be drawn, and where the current position moves afterwards (to the right, below, or to the beginning of the next line). To add a frame:
$pdf->Cell(40, 10, 'Hello World!', 1);
Finally, the document is closed and sent to the browser with Output(). One thing that trips developers up: the parameter order of Output() changed years ago. Since FPDF 1.8, the signature is Output($dest, $name) — destination first, filename second. Older tutorials (including the original version of this one) show the name-first order; FPDF detects the legacy order and swaps the values, so old code keeps working, but write new code destination-first. The destinations are: I to display inline in the browser (the default), D to force a download, F to save to a local file, and S to return the PDF document as a string — the one you want when attaching the PDF to an email with PHPMailer or Symfony Mailer.
$pdf->Output('F', 'invoice.pdf'); // save to file
$pdfString = $pdf->Output('S'); // return as string, e.g. for email attachment
There are many more functions in FPDF than we can cover here. To learn FPDF, browse fpdf.org.
FPDF’s limitation is the flip side of its strength: you position everything yourself. For a fixed-layout document like a certificate or a label, that precision is exactly what you want. For a document whose layout should come from HTML and CSS, you want a converter — which brings us to the library this article was originally built around.
HTML2FPDF: The Original Converter — Now Abandoned
HTML2FPDF is a PHP class library that uses the FPDF class library to convert HTML files to PDF files. When we first wrote this article, it was the practical way to do HTML-to-PDF conversion in PHP, and its usage looked like this:
<?php
// LEGACY CODE — do not use in production. Shown for historical reference.
require('html2fpdf.php');
$pdf = new HTML2FPDF();
$pdf->AddPage();
$strContent = file_get_contents('sample.html');
$pdf->WriteHTML($strContent);
$pdf->Output('sample.pdf');
Here is the honest part: HTML2FPDF has been abandoned for many years. The SourceForge project still exists as an archive, but the library predates PHP namespaces, Composer, and modern CSS, and it only ever worked reliably with XHTML 1.0. If you inherit a legacy application that still runs it — we occasionally do, in codebases handed over to us for maintenance — treat replacing it as technical debt to be paid, not a system to extend.
The good news is that HTML2FPDF did not simply die; it evolved. mPDF is its direct descendant — the mPDF project itself describes the library as based on FPDF and HTML2FPDF, with substantial enhancements: UTF-8 throughout, CSS support far beyond the original, headers and footers, and proper Composer packaging. If you are migrating old HTML2FPDF code, mPDF is the most natural landing place because the core concept — WriteHTML() in, PDF out — carried over:
composer require mpdf/mpdf
<?php
require __DIR__ . '/vendor/autoload.php';
$mpdf = new \Mpdf\Mpdf(['mode' => 'utf-8', 'format' => 'A4']);
$mpdf->WriteHTML(file_get_contents('sample.html'));
$mpdf->Output('sample.pdf', \Mpdf\Output\Destination::FILE);
mPDF’s particular strength is language support. For documents in Tamil, Arabic, Hindi, Chinese, or any script beyond Latin, it is the most dependable of the pure-PHP converters — something we can vouch for from running Tamil-language sites ourselves. Documentation is at mpdf.github.io. One caveat: mPDF is GPL-licensed, which is fine for internal applications and most client work, but check the license terms if you redistribute software that bundles it.
Dompdf: The Current Default for HTML to PDF in PHP
For new projects, Dompdf is where we start. It is an HTML layout and rendering engine written in PHP — mostly CSS 2.1 compliant, with a useful subset of CSS3 including @page rules for print margins. It reads external stylesheets, inline <style> tags, and style attributes, which means your designer can style the invoice template like any other HTML page.
composer require dompdf/dompdf
<?php
require __DIR__ . '/vendor/autoload.php';
use Dompdf\Dompdf;
use Dompdf\Options;
$options = new Options();
$options->set('isRemoteEnabled', false); // local assets only — safer default
$options->set('chroot', __DIR__); // restrict file access to this folder
$dompdf = new Dompdf($options);
$dompdf->loadHtml(file_get_contents('sample.html'));
$dompdf->setPaper('A4', 'portrait');
$dompdf->render();
$dompdf->stream('invoice.pdf', ['Attachment' => true]); // force download
// or: file_put_contents('invoice.pdf', $dompdf->output()); // save to disk
Two production notes from experience. First, leave isRemoteEnabled off and reference images by local path unless you genuinely need remote assets — several Dompdf security advisories over the years have involved remote resource loading. Second, Dompdf is not tolerant of sloppy markup, and a table row cannot break across pages; keep invoice line items as one row each and validate your template’s HTML. The project lives at github.com/dompdf/dompdf.
TCPDF and Headless Chrome: The Edge Cases
Two more options round out the landscape. TCPDF sits between FPDF and the HTML converters: programmatic drawing like FPDF, plus a writeHTML() method, plus features the others lack — digital signatures, PDF/A archival compliance, barcodes, and encryption. If a client requirement says “digitally signed” or “PDF/A”, TCPDF is usually the answer. It is heavier and its API shows its age, so we reach for it only when those features are required.
And when the requirement is “the PDF must look exactly like this web page” — flexbox, grid, web fonts, charts rendered by JavaScript — no pure-PHP engine will get you there. The honest answer is to render with a real browser: Spatie’s Browsershot drives headless Chrome from PHP and produces exactly what Chrome’s own “Print to PDF” would. The trade-off is infrastructure: you need Node and Chrome on the server, which rules it out on typical shared hosting. One related warning: wkhtmltopdf, the old command-line favorite for this job, is archived and unmaintained — do not build anything new on it.
Comparison: PHP PDF Libraries at a Glance
| Library | Best for | HTML/CSS support | Status in 2026 |
|---|---|---|---|
| FPDF | Programmatic PDFs: labels, certificates, fixed layouts | None — you draw everything | Maintained (v1.86, via setasign/fpdf) |
| HTML2FPDF | Nothing new — legacy maintenance only | XHTML 1.0 era | Abandoned — migrate to mPDF |
| Dompdf | Invoices, receipts, reports from HTML templates | CSS 2.1 + partial CSS3 | Actively maintained (v3.1.x) — our default |
| mPDF | UTF-8 and non-Latin scripts, headers/footers | Good CSS subset | Actively maintained (GPL license) |
| TCPDF | Digital signatures, PDF/A, barcodes | Basic HTML subset | Maintained |
| Browsershot / headless Chrome | Pixel-perfect rendering of modern CSS/JS pages | Full (it is Chrome) | Maintained; needs Node + Chrome on server |
Troubleshooting the Two Errors Everyone Hits
1. “Some data has already been output, can’t send PDF file”
FPDF, TCPDF, and mPDF all throw a variant of this, and the cause is always the same: something reached the browser before the PDF’s HTTP headers could be sent. The usual suspects, in the order we find them in client code:
- Whitespace or a blank line before
<?phpor after a closing?>in any included file — this is why modern PHP style omits the closing tag entirely. - A UTF-8 BOM at the start of a file. Editors sometimes add it invisibly; save as “UTF-8 without BOM”.
- An
echo,print_r(), or PHP warning emitted earlier in the request.
The quick diagnostic is to wrap the request in output buffering — ob_start() at the top, then ob_end_clean() immediately before calling Output() — which discards whatever stray output was queued. That gets you shipping again, but treat it as a bandage: find the file that leaks output, because the same leak will corrupt JSON responses and redirects too.
2. Output() confusion: parameter order and the ‘S’ destination
If your PDF downloads with the wrong filename, or saves when it should stream, you have the Output() parameters reversed. Since FPDF 1.8, it is Output($dest, $name) — destination first. And when you need the PDF as a value in your code rather than sent anywhere — to attach to an email, push to S3, or store in a database — use the S destination, which returns the document as a string:
$pdfString = $pdf->Output('S');
// TCPDF equivalent: $pdf->Output('doc.pdf', 'S');
// Dompdf equivalent: $dompdf->output();
Download the Working Sample
We have rebuilt the companion download for this article from scratch, tested on PHP 8.3. It contains a sample HTML invoice with a logo, ready-to-run scripts for FPDF, Dompdf, and mPDF, a composer.json, the generated PDFs so you can see the expected result, and a README with setup steps. Download the sample package here, or view the generated PDF first.
This article was originally published in the early years of Macronimous and has been thoroughly revised in July 2026 — the code samples were rewritten for PHP 8, tested before publishing, and the library recommendations reflect what we actually deploy for clients today.
If you are building PDF generation into a custom PHP web application — invoicing, reporting, or document workflows — our PHP team has been doing exactly this since 2002. We welcome you to contact us.