How to Add PDF Export to Any PHP Project — Generate Reports and Invoices with FPDF
A “Download as PDF” button transforms a PHP management system from a web page into a professional tool. This tutorial shows you how to add PDF export to any project using FPDF — the most beginner-friendly PHP PDF library, requiring no Composer, no dependencies, and no complex setup. By the end you will have a working invoice PDF generator you can attach to any table in your database.
Every PHP management system generates reports. Hospital systems need patient summary sheets. ISP systems need billing invoices. School systems need progress reports. Exam systems need results printouts. The common request that appears in every project is “can we download this as a PDF?”
FPDF is the answer for PHP beginners. It is a free, lightweight PHP class that generates PDF files programmatically. You do not need Composer, Laravel, or any framework. Download one file, include it, and start building PDFs in minutes. This tutorial builds three complete real-world PDF templates you can adapt for any project.
What You Will Build — Preview
Ref: INV-1042
Date: 20 April 2026
Due: 05 May 2026
Plan: 100Mbps Standard
| Description | Period | Amount |
|---|---|---|
| 100Mbps Standard Plan | April 2026 | £29.99 |
| Installation fee | One-time | £0.00 |
Step 1 — Download and Set Up FPDF
- Go to fpdf.org and click Download
- Unzip the downloaded file — you only need the
fpdf.phpfile and thefont/folder - Create a
libs/fpdf/folder in your project and copy both items there
Your project structure should look like this:
Project structure after adding FPDF
your-project/
├── config/dbconnection.php
├── libs/
│ └── fpdf/
│ ├── fpdf.php ← the main FPDF class
│ └── font/ ← font files (helvetica, times, etc.)
├── pdf/
│ └── generate_invoice.php ← NEW: your PDF generator
└── admin/billing.php ← existing page with download button
Step 2 — Your First “Hello PDF” in 10 Lines
Before building a full invoice, run this minimal example to confirm FPDF is installed correctly. Visit it directly in your browser — it should automatically download or display a PDF.
PHP pdf/test.php — confirm FPDF works before building anything complex
<?php
require_once '../libs/fpdf/fpdf.php';
$pdf = new FPDF(); // Create new PDF object
$pdf->AddPage(); // Add the first page
$pdf->SetFont('Helvetica', 'B', 20); // Font family, style (B=Bold), size
$pdf->Cell(0, 15, 'FPDF is Working!', 0, 1, 'C');
$pdf->SetFont('Helvetica', '', 12);
$pdf->Cell(0, 10, 'Your PHP project can now generate PDFs.', 0, 1, 'C');
$pdf->Output(); // 'I' = display in browser, 'D' = force download, 'S' = return as string
Understanding FPDF’s Cell() method
Cell($w, $h, $txt, $border, $ln, $align) — this is the workhorse function you will use constantly:
| Parameter | Meaning | Common Values |
|---|---|---|
$w | Width in mm. 0 = full page width | 0, 60, 90, 190 |
$h | Height in mm (row height) | 8, 10, 12, 15 |
$txt | The text to display | ‘Hello’, $variable |
$border | Cell border. 0=none, 1=all sides, ‘LTR’=specific sides | 0, 1, ‘B’, ‘LR’ |
$ln | Line break after. 0=same line, 1=next line, 2=under previous | 0, 1 |
$align | Text alignment | ‘L’, ‘C’, ‘R’ |
Step 3 — Complete Billing Invoice from Database
This generates a professional invoice PDF by fetching a specific bill from your database and formatting it with a header, billing details, line items table, and total.
PHP pdf/generate_invoice.php — complete billing invoice
<?php
require_once '../config/dbconnection.php';
require_once '../libs/fpdf/fpdf.php';
// ── Get bill ID from URL (e.g. /pdf/generate_invoice.php?id=42) ────
$bill_id = (int)($_GET['id'] ?? 0);
if (!$bill_id) { die('Invalid bill ID.'); }
// ── Fetch bill data from database ──────────────────────────────────
$stmt = mysqli_prepare($conn,
"SELECT b.*, c.name AS customer_name, c.email AS customer_email,
c.phone AS customer_phone, p.plan_name, p.price
FROM bills b
JOIN customers c ON b.customer_id = c.id
JOIN internet_plans p ON c.plan_id = p.id
WHERE b.id = ?");
mysqli_stmt_bind_param($stmt, 'i', $bill_id);
mysqli_stmt_execute($stmt);
$bill = mysqli_fetch_assoc(mysqli_stmt_get_result($stmt));
if (!$bill) { die('Bill not found.'); }
// ── Build the PDF ──────────────────────────────────────────────────
$pdf = new FPDF();
$pdf->AddPage();
$pdf->SetMargins(15, 15, 15);
// HEADER — Company name + Invoice title
$pdf->SetFillColor(0, 87, 184); // #0057b8 blue
$pdf->SetTextColor(255, 255, 255);
$pdf->SetFont('Helvetica', 'B', 16);
$pdf->Cell(0, 14, 'Swift Internet Services', 0, 1, 'L', true);
$pdf->SetFont('Helvetica', '', 9);
$pdf->SetTextColor(0, 0, 0);
$pdf->Ln(3);
// Invoice reference + date (two columns)
$pdf->SetFont('Helvetica', 'B', 14);
$pdf->Cell(90, 10, 'INVOICE', 0, 0, 'L');
$pdf->SetFont('Helvetica', '', 10);
$pdf->Cell(90, 10, 'Ref: INV-' . str_pad($bill_id, 4, '0', STR_PAD_LEFT), 0, 1, 'R');
$pdf->SetFont('Helvetica', '', 10);
$pdf->Cell(90, 8, 'support@swiftnet.com', 0, 0, 'L');
$pdf->Cell(90, 8, 'Date: ' . date('d M Y'), 0, 1, 'R');
$pdf->Cell(90, 8, '', 0, 0);
$pdf->Cell(90, 8, 'Due: ' . date('d M Y', strtotime('+14 days')), 0, 1, 'R');
$pdf->Ln(5);
// Divider line
$pdf->SetDrawColor(0, 87, 184);
$pdf->SetLineWidth(0.5);
$pdf->Line(15, $pdf->GetY(), 195, $pdf->GetY());
$pdf->Ln(4);
// Bill To section
$pdf->SetFont('Helvetica', 'B', 10);
$pdf->Cell(0, 7, 'BILL TO', 0, 1);
$pdf->SetFont('Helvetica', '', 10);
$pdf->Cell(0, 6, $bill['customer_name'], 0, 1);
$pdf->Cell(0, 6, $bill['customer_email'], 0, 1);
$pdf->Cell(0, 6, 'Plan: ' . $bill['plan_name'], 0, 1);
$pdf->Ln(5);
// Table header row
$pdf->SetFillColor(0, 87, 184);
$pdf->SetTextColor(255, 255, 255);
$pdf->SetFont('Helvetica', 'B', 10);
$pdf->Cell(90, 9, 'Description', 0, 0, 'L', true);
$pdf->Cell(50, 9, 'Period', 0, 0, 'C', true);
$pdf->Cell(40, 9, 'Amount', 0, 1, 'R', true);
// Table data rows
$pdf->SetTextColor(0, 0, 0);
$pdf->SetFont('Helvetica', '', 10);
$pdf->SetFillColor(240, 246, 255);
$pdf->Cell(90, 8, $bill['plan_name'], 'B', 0, 'L', true);
$pdf->Cell(50, 8, date('F Y', strtotime($bill['bill_month'])), 'B', 0, 'C', true);
$pdf->Cell(40, 8, '£' . number_format($bill['amount'], 2), 'B', 1, 'R', true);
$pdf->Ln(3);
$pdf->SetFont('Helvetica', 'B', 12);
$pdf->Cell(0, 10, 'Total Due: £' . number_format($bill['amount'], 2), 0, 1, 'R');
// Footer note
$pdf->Ln(8);
$pdf->SetFont('Helvetica', 'I', 9);
$pdf->SetTextColor(120, 120, 120);
$pdf->Cell(0, 6, 'Thank you for your business. Please pay by the due date shown above.', 0, 1, 'C');
// Output: 'D' = force download, 'I' = show in browser
$filename = 'Invoice_INV-' . str_pad($bill_id, 4, '0', STR_PAD_LEFT) . '.pdf';
$pdf->Output('D', $filename); // 'D' forces download to user's device
Step 4 — Multi-Row Data Table Report
This pattern generates a PDF with a table of all records from a database query — useful for customer reports, exam results, inventory summaries, or attendance records.
PHP pdf/customer_report.php — all customers as a PDF table
<?php
require_once '../config/dbconnection.php';
require_once '../libs/fpdf/fpdf.php';
$result = mysqli_query($conn,
"SELECT c.id, c.name, c.email, p.plan_name, p.price, c.status
FROM customers c
JOIN internet_plans p ON c.plan_id = p.id
ORDER BY c.name ASC");
$pdf = new FPDF('L', 'mm', 'A4'); // Landscape orientation for wide tables
$pdf->AddPage();
$pdf->SetMargins(10, 10, 10);
$pdf->SetAutoPageBreak(true, 15); // Auto-add pages when content overflows
// Report title
$pdf->SetFont('Helvetica', 'B', 14);
$pdf->Cell(0, 10, 'Customer Report — ' . date('d M Y'), 0, 1, 'C');
$pdf->SetFont('Helvetica', '', 9);
$pdf->Cell(0, 6, 'Generated by Swift Internet Services Management System', 0, 1, 'C');
$pdf->Ln(4);
// Column headers
$pdf->SetFillColor(0, 87, 184);
$pdf->SetTextColor(255, 255, 255);
$pdf->SetFont('Helvetica', 'B', 9);
$cols = ['ID'=>12, 'Customer Name'=>65, 'Email'=>75, 'Plan'=>50, 'Price'=>25, 'Status'=>30];
foreach ($cols as $name => $w) {
$pdf->Cell($w, 8, $name, 0, 0, 'C', true);
}
$pdf->Ln();
// Data rows with alternating background
$pdf->SetTextColor(0, 0, 0);
$pdf->SetFont('Helvetica', '', 9);
$row_i = 0;
while ($row = mysqli_fetch_assoc($result)) {
$fill = $row_i % 2 === 0;
if ($fill) $pdf->SetFillColor(240, 246, 255);
else $pdf->SetFillColor(255, 255, 255);
$pdf->Cell(12, 7, $row['id'], 'B', 0, 'C', $fill);
$pdf->Cell(65, 7, $row['name'], 'B', 0, 'L', $fill);
$pdf->Cell(75, 7, $row['email'], 'B', 0, 'L', $fill);
$pdf->Cell(50, 7, $row['plan_name'], 'B', 0, 'C', $fill);
$pdf->Cell(25, 7, '£'.$row['price'], 'B', 0, 'R', $fill);
$pdf->Cell(30, 7, ucfirst($row['status']),'B', 1, 'C', $fill);
$row_i++;
}
// Row count footer
$pdf->Ln(3);
$pdf->SetFont('Helvetica', 'I', 8);
$pdf->Cell(0, 5, 'Total records: ' . $row_i, 0, 1, 'R');
$pdf->Output('D', 'Customer_Report_' . date('Y-m-d') . '.pdf');
Step 5 — Add a Download Button to Your Existing Pages
Add this button anywhere on your billing management page. Clicking it calls the PDF generator with the bill’s ID and automatically downloads the PDF.
PHP Inside your bills table — one download button per row
<?php while ($bill = mysqli_fetch_assoc($bills_result)): ?>
<tr>
<td><?= htmlspecialchars($bill['ref']) ?></td>
<td><?= htmlspecialchars($bill['customer_name']) ?></td>
<td>£<?= number_format($bill['amount'], 2) ?></td>
<td><?= htmlspecialchars($bill['status']) ?></td>
<td>
<!-- This button opens the PDF generator in a new tab -->
<a href="/pdf/generate_invoice.php?id=<?= (int)$bill['id'] ?>"
target="_blank"
class="btn btn-sm btn-outline-danger">
📄 Download PDF
</a>
</td>
</tr>
<?php endwhile; ?>
Add a “Download Full Report” button to your reports page
PHP Button that downloads all records as a PDF
<!-- Add near the top of your customer list / reports page -->
<a href="/pdf/customer_report.php"
class="btn btn-danger">
📥 Download Customer Report (PDF)
</a>
<!-- Or with date filters passed as GET parameters: -->
<a href="/pdf/customer_report.php?from=2026-01-01&to=2026-12-31"
class="btn btn-danger">
📥 Download 2026 Annual Report
</a>
Step 6 — Send PDF as Email Attachment
PHP Generate PDF and email it — combines FPDF + PHPMailer
require_once '../libs/fpdf/fpdf.php';
require_once '../helpers/mailer.php';
function emailInvoicePDF($bill) {
// Step 1: Generate the PDF and save to a temp file
$pdf = new FPDF();
$pdf->AddPage();
// ... build your PDF here (same as Step 3) ...
// Save to temp file (not the browser output)
$tmpFile = sys_get_temp_dir() . '/invoice_' . $bill['id'] . '.pdf';
$pdf->Output('F', $tmpFile); // 'F' = save to file path (not browser)
// Step 2: Email it using the sendEmail() function from Post 2
$emailBody = '<p>Dear ' . htmlspecialchars($bill['customer_name'])
. ,</p><p>Please find your invoice attached.</p>';
$result = sendEmail(
$bill['customer_email'],
$bill['customer_name'],
'Your Invoice for ' . date('F Y'),
$emailBody,
'',
[$tmpFile] // Pass temp file as attachment
);
// Step 3: Clean up temp file
if (file_exists($tmpFile)) unlink($tmpFile);
return $result;
}
Frequently Asked Questions
Text with special characters (£, €, é, ñ) shows as garbled characters in my PDF.
FPDF’s core fonts (Helvetica, Arial, Times) have limited Unicode support. For special characters, use iconv() to convert the string: $pdf->Cell(0, 8, iconv('UTF-8', 'ISO-8859-1', $text)). For full Unicode support including Arabic, Chinese, and full European character sets, switch to TCPDF which has built-in UTF-8 support — the code structure is very similar to FPDF so migrating is straightforward.
My PDF outputs garbled characters instead of the PDF in the browser.
This is almost always caused by whitespace or output before the PDF headers. Check for: a BOM (byte order mark) at the top of your PHP file (common in Windows text editors — use UTF-8 without BOM), any echo or print statements before the PDF output, a blank line after the closing ?> tag, or extra whitespace at the beginning of included files. The PDF output requires that nothing at all has been sent to the browser before $pdf->Output() is called. If using output buffering (ob_start() at the top of the file), call ob_end_clean() just before the Output() call.
How do I add a page number to every page?
Extend the FPDF class and override the Footer() method: class MyPDF extends FPDF { function Footer() { $this->SetY(-12); $this->SetFont('Helvetica','I',8); $this->Cell(0,10,'Page '.$this->PageNo().'/{nb}',0,0,'C'); } }. Then use $pdf = new MyPDF(); $pdf->AliasNbPages(); $pdf->AddPage();. The AliasNbPages() call enables the {nb} placeholder for total page count.
Can I add my logo image to the PDF?
Yes. FPDF supports PNG, JPEG, and GIF images. Use: $pdf->Image('/path/to/logo.png', $x, $y, $width); where x and y are the position in mm from the top-left of the page, and width is the desired width in mm. Add it in your header section, typically in the top-left corner. Make sure the image path is an absolute server path (use $_SERVER['DOCUMENT_ROOT']) not a relative URL.
Email the generated PDF as an attachment
Let users filter before downloading the report
Add this invoice generator to your ISP project
Alternative: offer Excel downloads alongside PDFs
Last updated April 2026. FPDF 1.86 used. Tested on PHP 8.2 / XAMPP. FPDF available at fpdf.org (free, open source, no dependencies).


