Pdf Generation
By the end of this lesson you'll turn HTML and CSS into a polished invoice PDF, stamp every page with a header, footer and page number, drop in a logo, and choose between streaming a download or saving the file — the exact skills behind every "Download as PDF" button you've ever clicked.
Part of the free Php course at LearnCodingFast — hands-on lessons with examples you run in your browser, plus practice exercises and a quick quiz.
What You'll Learn in This Lesson
1️⃣ Choosing a PDF Library
PHP has no built-in PDF maker, so you add one with Composer (PHP's package manager). The three families you'll meet: Dompdf and mPDF turn an HTML + CSS template into a PDF — ideal when you can describe the document as a web page. TCPDF and the classic FPDF are programmatic : you place each piece of text, box and image at exact coordinates. mPDF is the pick when you need strong Unicode or right-to-left scripts like Arabic. Start from how you want to describe the page, and the choice falls out.
2️⃣ HTML → PDF: an Invoice with Dompdf
This is the most common real-world path. You build the invoice as a normal HTML string with a <style> block, then Dompdf renders it to PDF bytes. Two things to notice: the CSS lives inside the HTML (Dompdf won't fetch a separate .css file), and output() hands you the raw bytes so you decide whether to save, stream, or email them.
The PDF will look almost exactly like the HTML would in a browser — same green header, same table — which is why Dompdf is so productive. The catch is that it understands only a subset of CSS (no flexbox, no grid); you'll see how to dodge that in Common Errors.
3️⃣ Headers, Footers, Page Numbers & Images
Multi-page documents need the same logo and page numbers on every page. With TCPDF you get this almost for free: override the Header() and Footer() methods and TCPDF calls them automatically on each new page. Image() drops a logo at exact coordinates, and getAliasNumPage() / getAliasNbPages() fill in "Page X of Y" — TCPDF substitutes the real totals after the whole document is built.
4️⃣ Streaming vs Saving
Once the PDF exists you must say where it goes . Stream it and the bytes flow to the browser — either viewed inline in the tab or popped as a download dialog. Save it and the bytes land in a file you can store, attach to an email, or serve later. The destination is just a flag — Dompdf's stream() vs output() , or TCPDF's Output(name, "I"|"D"|"F"|"S") — but there's one golden rule beside it.
The golden rule: when you stream, the PDF bytes must be the first thing the server sends. A stray echo , a blank line, or even a space before <?php sneaks ahead of the PDF and corrupts it — the browser downloads a file that won't open. If you don't need to stream, use output() / Output("", "S") to grab the bytes safely.
5️⃣ Your Turn: Finish the Dompdf Script
Now you try. The script below is wired up except for the three method calls that actually do the work. Fill in each ___ using the 👉 hint, then run it in a project where you've installed Dompdf and check it against the Output panel.
One more — this time a TCPDF footer that needs to show "Page X of Y" . Fill in the two helpers that return the current page and the total.
Common Errors (and the fix)
- Your layout collapses — flexbox / grid is ignored — Dompdf is not a browser and supports only a subset of CSS. display: flex and grid simply do nothing. Lay PDFs out with tables and fixed widths, and put CSS in a <style> block or inline — external .css files aren't fetched by default.
- "Allowed memory size of … bytes exhausted" — a big table or a high-resolution image blew past PHP's memory_limit while rendering. Raise it for the script with ini_set('memory_limit', '256M') , shrink images before embedding them, and split huge reports across pages instead of one giant page.
- £, €, accents or emoji show as empty boxes — the active font has no glyph for those characters. Use a font with wide coverage ( DejaVu Sans ships with both Dompdf and TCPDF) and make sure your HTML is UTF-8. For Arabic or Hebrew, switch to mPDF .
- The downloaded PDF won't open / "file is damaged" — something was printed before the PDF bytes. When streaming, the PDF must be the first output, so remove any stray echo , blank line, or space before <?php (and drop the closing ?> tag). Or save with output() instead of streaming.
Pro Tips
- 💡 Generate slow PDFs in the background. A 10-page report can take several seconds — queue it as a background job and email the user a download link instead of blocking the request.
- 💡 Design the template in a browser first. Build and tweak the HTML in Chrome, then hand it to Dompdf — but keep to tables and simple CSS so it survives the conversion.
- 💡 Set a Unicode default font once. DejaVu Sans covers Latin, currency symbols and accents, so you avoid the empty-box surprise on a customer's name.
📋 Quick Reference — PHP PDF Generation
Task
Code
Notes
Install Dompdf
composer require dompdf/dompdf
HTML + CSS → PDF
Install TCPDF
composer require tecnickcom/tcpdf
Coordinate-based
Render HTML
$d->loadHtml($h); $d->render();
Dompdf
Save bytes
file_put_contents($f, $d->output());
to a file
Download
$d->stream("x.pdf");
browser download
TCPDF page
$p->AddPage(); $p->Cell(...);
add page + text
Page numbers
getAliasNumPage() / getAliasNbPages()
in Footer()
TCPDF dest
$p->Output($n, "I"|"D"|"F"|"S");
view/down/file/string
Frequently Asked Questions
Mini-Challenge: A Receipt PDF
No code is filled in this time — just a brief and an outline. Write it yourself in a project with Dompdf installed, run it, then check your result against the expected output in the comments. This is exactly the build-render-save loop you'll use on every real document.
🎉 Lesson Complete!
- ✅ PHP makes PDFs through Composer libraries — there's no built-in maker
- ✅ Dompdf / mPDF convert HTML + CSS; TCPDF / FPDF place elements by coordinate
- ✅ Build invoices from an HTML template, keeping CSS inline or in a <style> block
- ✅ Override Header() / Footer() for logos and "Page X of Y" on every page
- ✅ Stream bytes to the browser or save them — but stream nothing before the PDF
- ✅ Watch the gotchas: limited CSS, memory on big PDFs, and Unicode fonts
- ✅ Next lesson: Localization — build multilingual PHP apps with translations and locale-aware formatting
Practice quiz
How does a PHP app gain the ability to generate PDFs?
- It is built into PHP core
- You enable the pdf extension in php.ini
- You add a library with Composer — PHP has no built-in PDF maker
- You shell out to a system command only
Answer: You add a library with Composer — PHP has no built-in PDF maker. PHP can't make a PDF on its own; you pull in a Composer library like Dompdf, mPDF, or TCPDF.
Which library is best when you already have an HTML + CSS template to convert?
- Dompdf (or mPDF)
- TCPDF
- FPDF
- ImageMagick
Answer: Dompdf (or mPDF). Dompdf and mPDF render an HTML + CSS template to PDF — the quickest path for most invoices and reports.
Which library is the pick when you need strong Unicode, emoji, or right-to-left scripts like Arabic?
- Dompdf
- FPDF
- TCPDF
- mPDF
Answer: mPDF. mPDF has the strongest Unicode and RTL text handling of the libraries covered.
When should you reach for TCPDF or FPDF?
- When you want the fastest possible HTML conversion
- When you need pixel-exact layout, placing every line, box, and image by coordinate
- When you only need plain text output
- When you can't install Composer
Answer: When you need pixel-exact layout, placing every line, box, and image by coordinate. TCPDF and FPDF are programmatic — you position each element by coordinate for pixel-perfect documents like labels or forms.
Where must CSS live for a Dompdf template, and why?
- Inside the HTML (a <style> block or inline), because Dompdf does NOT fetch external .css files by default
- In a separate linked .css file, which Dompdf fetches automatically
- In the php.ini configuration
- In a JSON file alongside the script
Answer: Inside the HTML (a <style> block or inline), because Dompdf does NOT fetch external .css files by default. Dompdf won't fetch external stylesheets by default, so CSS goes in a <style> block or inline.
In Dompdf, which call hands you the raw PDF bytes so you decide whether to save, stream, or email?
- render()
- loadHtml()
- output()
- setPaper()
Answer: output(). output() returns the raw bytes; you then file_put_contents() them, stream them, or attach them to email.
In the Dompdf workflow, what is the correct order of method calls?
- render() then loadHtml() then output()
- loadHtml() then render() then output()
- output() then loadHtml() then render()
- setPaper() then output() then render()
Answer: loadHtml() then render() then output(). Load the HTML, render it to PDF bytes, then output() (or save) the bytes.
In TCPDF, why override the Header() and Footer() methods?
- To connect to the database
- To validate user input
- To set the PDF password
- Because TCPDF calls them automatically on every new page, giving you repeating logos and page numbers for free
Answer: Because TCPDF calls them automatically on every new page, giving you repeating logos and page numbers for free. TCPDF invokes Header() and Footer() on each page automatically — perfect for a logo and 'Page X of Y' everywhere.
Which TCPDF helpers produce the 'Page X of Y' numbers in a footer?
- getPage() and countPages()
- getAliasNumPage() and getAliasNbPages()
- currentPage() and totalPages()
- pageNum() and pageTotal()
Answer: getAliasNumPage() and getAliasNbPages(). getAliasNumPage() gives the current page and getAliasNbPages() the total; TCPDF substitutes the real totals after building.
Why might a streamed PDF download but refuse to open ('file is damaged')?
- The PDF was too large
- The wrong paper size was chosen
- Something was output before the PDF bytes — a stray echo, blank line, or space before <?php — when streaming, the bytes must be first
- The font was too small
Answer: Something was output before the PDF bytes — a stray echo, blank line, or space before <?php — when streaming, the bytes must be first. When streaming, the PDF bytes must be the very first output; any prior output corrupts the file. Use output() to save instead.
Continue this course
- Previous: Image Processing
- Next: Localization