Learning how to create PDF files with Python is useful when you need to automate invoices, certificates, reports, receipts, labels, or personalized documents. The FPDF family of libraries provides a direct way to build a PDF page by page with text, images, lines, tables, headers, and footers.
This beginner guide uses the modern fpdf2 package, which is imported with from fpdf import FPDF. You will install the library, create a first document, control fonts and page layout, add images, build tables, handle page breaks, generate reports from data, and avoid common encoding and file-path errors.
Basic knowledge of Python functions, pathlib, and loops will make the examples easier to follow.
What Is FPDF?
FPDF is a lightweight approach to PDF generation. Your program defines the document size, margins, fonts, and content, and the library writes a PDF file. The current fpdf2 documentation includes the complete API, tutorials, and layout examples. The pathlib documentation is also useful for reliable output paths.
PDF generation is different from editing an existing document. FPDF is strongest when your application creates a new file from structured data.
Install fpdf2
Create a virtual environment and install the package:
python -m pip install fpdf2Then confirm the import:
from fpdf import FPDF
print("FPDF is ready")The package name is fpdf2, but the Python import remains fpdf. The virtual environment guide explains how to isolate dependencies for each project.
Create Your First PDF
from fpdf import FPDF
pdf = FPDF()
pdf.add_page()
pdf.set_font("Helvetica", size=16)
pdf.cell(text="Hello from Python!")
pdf.output("hello.pdf")The basic sequence is simple:
- Create an
FPDFobject. - Add at least one page.
- Select a font.
- Add content.
- Write the document with
output().
Choose Page Size and Orientation
from fpdf import FPDF
pdf = FPDF(orientation="P", unit="mm", format="A4")
pdf.add_page()P means portrait and L means landscape. Millimeters are convenient for print documents, while points may be useful when matching design specifications.
Add a Title and Paragraphs
cell() is useful for short lines. multi_cell() wraps longer text automatically.
from fpdf import FPDF
pdf = FPDF()
pdf.set_auto_page_break(auto=True, margin=15)
pdf.add_page()
pdf.set_font("Helvetica", style="B", size=20)
pdf.cell(0, 12, "Monthly Report", new_x="LMARGIN", new_y="NEXT", align="C")
pdf.ln(6)
pdf.set_font("Helvetica", size=11)
pdf.multi_cell(
0,
6,
"This report was generated automatically with Python. "
"Long text is wrapped across multiple lines by multi_cell().",
)
pdf.output("monthly-report.pdf")Automatic page breaking prevents content from continuing below the printable area.
Use Margins and Spacing
pdf.set_margins(left=18, top=18, right=18)
pdf.set_auto_page_break(auto=True, margin=18)Use ln() to create vertical space, and avoid positioning every element with hard-coded coordinates unless the document requires a fixed form layout.
Add Headers and Footers
Create a subclass when every page needs repeated content:
from fpdf import FPDF
class ReportPDF(FPDF):
def header(self):
self.set_font("Helvetica", style="B", size=10)
self.cell(0, 8, "Academify Report", align="C")
self.ln(12)
def footer(self):
self.set_y(-15)
self.set_font("Helvetica", style="I", size=9)
self.cell(0, 10, f"Page {self.page_no()}", align="C")The library calls header() and footer() automatically as pages are created.
Add an Image
from pathlib import Path
from fpdf import FPDF
logo = Path("assets") / "logo.png"
pdf = FPDF()
pdf.add_page()
if logo.exists():
pdf.image(str(logo), x=15, y=12, w=35)
pdf.set_y(55)
pdf.set_font("Helvetica", size=12)
pdf.cell(0, 8, "Document with an image", new_x="LMARGIN", new_y="NEXT")
pdf.output("document-with-image.pdf")Always verify that an image exists before inserting it. The pathlib guide covers portable paths and file checks.
Create a Simple Table
from fpdf import FPDF
rows = [
("Product", "Quantity", "Price"),
("Keyboard", "2", "$90.00"),
("Mouse", "3", "$60.00"),
("Monitor", "1", "$240.00"),
]
pdf = FPDF()
pdf.add_page()
pdf.set_font("Helvetica", size=10)
widths = [80, 40, 50]
for row_index, row in enumerate(rows):
if row_index == 0:
pdf.set_font("Helvetica", style="B", size=10)
else:
pdf.set_font("Helvetica", size=10)
for value, width in zip(row, widths):
pdf.cell(width, 9, value, border=1)
pdf.ln()
pdf.output("products.pdf")zip() combines each value with its column width. See the Python zip guide for more examples.
Generate a PDF from a List of Dictionaries
from fpdf import FPDF
sales = [
{"customer": "Ana", "total": 120.50},
{"customer": "Jordan", "total": 89.90},
{"customer": "Sam", "total": 215.00},
]
pdf = FPDF()
pdf.set_auto_page_break(auto=True, margin=15)
pdf.add_page()
pdf.set_font("Helvetica", style="B", size=18)
pdf.cell(0, 12, "Sales Summary", new_x="LMARGIN", new_y="NEXT")
pdf.set_font("Helvetica", size=11)
for sale in sales:
line = f"Customer: {sale['customer']} | Total: ${sale['total']:.2f}"
pdf.cell(0, 8, line, new_x="LMARGIN", new_y="NEXT")
pdf.output("sales-summary.pdf")This pattern works well when data comes from a CSV file, database, API, or Pandas DataFrame. The CSV guide and Pandas guide explain common data sources.
Create a Reusable Report Function
from pathlib import Path
from fpdf import FPDF
def create_report(title: str, lines: list[str], output: Path) -> Path:
output.parent.mkdir(parents=True, exist_ok=True)
pdf = FPDF()
pdf.set_auto_page_break(auto=True, margin=15)
pdf.add_page()
pdf.set_font("Helvetica", style="B", size=18)
pdf.cell(0, 12, title, new_x="LMARGIN", new_y="NEXT")
pdf.ln(4)
pdf.set_font("Helvetica", size=11)
for line in lines:
pdf.multi_cell(0, 6, line)
pdf.ln(2)
pdf.output(str(output))
return output
created_file = create_report(
"Project Notes",
["First item", "Second item", "Third item"],
Path("output") / "notes.pdf",
)
print(created_file.resolve())A focused function is easier to test and reuse. The functions guide explains parameters, return values, and clean function design.
Handle Unicode and Custom Fonts
Built-in PDF fonts do not cover every Unicode character. For multilingual text, register a Unicode font file that your project is licensed to distribute.
from fpdf import FPDF
pdf = FPDF()
pdf.add_page()
pdf.add_font("DejaVu", fname="fonts/DejaVuSans.ttf")
pdf.set_font("DejaVu", size=12)
pdf.multi_cell(0, 7, "Text with international characters: café, ação, 日本語")
pdf.output("unicode.pdf")Keep font files inside a predictable project directory and check the path before generating the document. Do not assume a font installed on your computer exists on a server.
Build an Invoice
A basic invoice normally contains a seller, customer, item rows, totals, date, and payment information. Separate calculation from presentation so the PDF layer receives already validated values.
items = [
{"description": "Consulting", "quantity": 3, "unit_price": 80.0},
{"description": "Support", "quantity": 2, "unit_price": 45.0},
]
total = sum(item["quantity"] * item["unit_price"] for item in items)
print(f"Invoice total: ${total:.2f}")Then render the values into rows. This separation makes calculations testable with Pytest.
Handle Errors Clearly
from pathlib import Path
output = Path("reports") / "result.pdf"
try:
output.parent.mkdir(parents=True, exist_ok=True)
pdf.output(str(output))
except PermissionError:
print("The PDF cannot be written to this folder.")
except OSError as error:
print(f"Could not create the PDF: {error}")Typical failures include missing images, missing font files, invalid output folders, permissions, and unsupported characters. Catch only errors you can explain or recover from.
Common Mistakes
- Forgetting to call
add_page(). - Writing text before selecting a font.
- Using a font that does not contain required characters.
- Assuming relative paths point to the script directory.
- Creating fixed coordinates that overlap when text wraps.
- Not enabling automatic page breaks.
- Mixing calculations and PDF layout in one long function.
- Overwriting an existing document without checking.
Frequently Asked Questions
What should I install: fpdf or fpdf2?
For modern projects, install fpdf2. The import statement remains from fpdf import FPDF.
Can FPDF edit an existing PDF?
It is primarily designed to generate new PDFs. Editing or merging existing documents may require a different PDF library.
Can I add charts?
Yes. Save a chart as an image and insert it with image().
Can I create multi-page reports?
Yes. Enable automatic page breaks and use headers and footers for repeated content.
Does it support accented and international text?
Yes, when you register a Unicode font that contains the needed characters.
Conclusion
FPDF provides a practical way to create PDF files with Python. Start with a page, font, and a few cells, then add multi-line text, images, tables, headers, footers, and automatic page breaks.
For reliable applications, keep calculations separate from layout, use pathlib for file locations, validate external assets, and register an appropriate font for Unicode text. These habits turn a small PDF script into a reusable reporting system.






