Why Generate PDFs with Python?
Programmatic PDF generation is essential for:
- Invoices and receipts — auto-generate from order data
- Reports — create formatted reports from analytics
- Certificates — batch-produce certificates with dynamic names
- Letters and contracts — mail merge at scale
ReportLab is the most mature Python library for PDF creation. It is open-source, well-documented, and used in production by companies like NASA and Wikipedia.
Installation
Install ReportLab via pip:
pip install reportlab
Verify the installation:
import reportlab
print(reportlab.Version) # Should print 4.x
Creating Your First PDF
The simplest possible PDF:
from reportlab.pdfgen import canvas
def create_simple_pdf(filename):
c = canvas.Canvas(filename)
c.setFont("Helvetica-Bold", 24)
c.drawString(72, 750, "Hello, PDF!")
c.setFont("Helvetica", 14)
c.drawString(72, 720, "Generated with Python and ReportLab")
c.save()
create_simple_pdf("hello.pdf")
This creates a single-page PDF with text. The coordinates (72, 750) represent points from the bottom-left corner. 72 points = 1 inch.
Building a Professional Invoice
Here is a complete invoice generator:
from reportlab.lib.pagesizes import A4
from reportlab.lib.units import mm
from reportlab.lib import colors
from reportlab.platypus import SimpleDocTemplate, Table, TableStyle, Paragraph, Spacer
from reportlab.lib.styles import getSampleStyleSheet, ParagraphStyle
def create_invoice(filename, invoice_data):
doc = SimpleDocTemplate(filename, pagesize=A4,
topMargin=20*mm, bottomMargin=20*mm,
leftMargin=20*mm, rightMargin=20*mm)
styles = getSampleStyleSheet()
elements = []
# Title
title_style = ParagraphStyle("InvoiceTitle", parent=styles["Heading1"],
fontSize=28, textColor=colors.HexColor("#1a1a2e"))
elements.append(Paragraph("INVOICE", title_style))
elements.append(Spacer(1, 10*mm))
# Invoice details
details = f"""
Invoice #: {invoice_data["number"]}
Date: {invoice_data["date"]}
Client: {invoice_data["client"]}
"""
elements.append(Paragraph(details, styles["Normal"]))
elements.append(Spacer(1, 10*mm))
# Items table
table_data = [["Item", "Qty", "Price", "Total"]]
for item in invoice_data["items"]:
total = item["qty"] * item["price"]
table_data.append([item["name"], str(item["qty"]),
f"${item['price']:.2f}", f"${total:.2f}"])
grand_total = sum(i["qty"] * i["price"] for i in invoice_data["items"])
table_data.append(["", "", "Total:", f"${grand_total:.2f}"])
table = Table(table_data, colWidths=[80*mm, 20*mm, 30*mm, 30*mm])
table.setStyle(TableStyle([
("BACKGROUND", (0, 0), (-1, 0), colors.HexColor("#1a1a2e")),
("TEXTCOLOR", (0, 0), (-1, 0), colors.white),
("FONTNAME", (0, 0), (-1, 0), "Helvetica-Bold"),
("FONTSIZE", (0, 0), (-1, 0), 11),
("BOTTOMPADDING", (0, 0), (-1, 0), 8),
("GRID", (0, 0), (-1, -1), 0.5, colors.grey),
("FONTNAME", (0, -1), (-1, -1), "Helvetica-Bold"),
("ALIGN", (1, 0), (-1, -1), "RIGHT"),
]))
elements.append(table)
doc.build(elements)
# Usage
invoice = {
"number": "INV-2026-001",
"date": "March 15, 2026",
"client": "Acme Corp",
"items": [
{"name": "Web Development", "qty": 40, "price": 150.00},
{"name": "UI Design", "qty": 20, "price": 120.00},
{"name": "Hosting (Annual)", "qty": 1, "price": 299.00},
]
}
create_invoice("invoice.pdf", invoice)
Adding Headers and Footers
For multi-page documents, add consistent headers and footers:
def header_footer(canvas, doc):
canvas.saveState()
# Header
canvas.setFont("Helvetica-Bold", 10)
canvas.setFillColor(colors.HexColor("#1a1a2e"))
canvas.drawString(20*mm, A4[1] - 15*mm, "Your Company Name")
canvas.setFont("Helvetica", 8)
canvas.drawRightString(A4[0] - 20*mm, A4[1] - 15*mm, "www.yourcompany.com")
canvas.line(20*mm, A4[1] - 17*mm, A4[0] - 20*mm, A4[1] - 17*mm)
# Footer
canvas.setFont("Helvetica", 8)
canvas.setFillColor(colors.grey)
canvas.drawCentredString(A4[0] / 2, 10*mm,
f"Page {canvas.getPageNumber()}")
canvas.restoreState()
# Use it when building
doc.build(elements, onFirstPage=header_footer, onLaterPages=header_footer)
Adding Images to PDFs
Include logos or photos in your PDF:
from reportlab.platypus import Image as RLImage
# Add a logo
logo = RLImage("logo.png", width=40*mm, height=15*mm)
elements.insert(0, logo) # Add at the beginning
# Add a photo with aspect ratio preserved
from reportlab.lib.utils import ImageReader
img = ImageReader("photo.jpg")
iw, ih = img.getSize()
aspect = ih / iw
photo = RLImage("photo.jpg", width=120*mm, height=120*mm * aspect)
elements.append(photo)
FAQ
Yes, but you need to register TTF fonts that support those characters. Use pdfmetrics.registerFont() with a Unicode-compatible font file.
ReportLab creates new PDFs. To modify existing ones, use PyPDF2 or pikepdf. You can also use Reformat's free online PDF tools for merging, compressing, and converting.
Is ReportLab free?Yes, the open-source version is free under the BSD license. There is also a commercial version (ReportLab PLUS) with additional features like charts and barcodes.