Python

How to Create a PDF Generator with Python and ReportLab

Build a PDF generator in Python using ReportLab. Learn to create invoices, reports, and documents with text, tables, images, headers, footers, and custom styling.

March 16, 20269 min read

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

Can ReportLab handle Unicode and non-Latin scripts?

Yes, but you need to register TTF fonts that support those characters. Use pdfmetrics.registerFont() with a Unicode-compatible font file.

How do I convert existing PDFs or modify them?

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.

pythonpdfreportlabautomation

Related Tutorials