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.
Learn Python, JavaScript, Java and more with free interactive lessons, real projects and AI-powered help. Beginner-friendly.
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.
📋 Quick Reference — PHP PDF Generation
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.
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.