Skip to main content

How to Create PDF Documents in Python with ReportLab

ReportLab is the industry-standard library for generating PDF documents programmatically in Python. It's widely used for creating invoices, reports, certificates, and automated documentation.

Installation

Install ReportLab from PyPI:

pip install reportlab

Basic PDF with Canvas

The Canvas object provides a drawing surface where you position elements using X, Y coordinates. The origin (0, 0) is at the bottom-left corner:

from reportlab.pdfgen import canvas

def create_simple_pdf(filename):
c = canvas.Canvas(filename)

# Draw text at position (100, 750)
c.drawString(100, 750, "Hello World from Python!")

# Draw a horizontal line
c.line(100, 740, 400, 740)

# Save the PDF
c.save()

create_simple_pdf("hello.pdf")

Working with Page Sizes

Use predefined page sizes for consistent document dimensions:

from reportlab.pdfgen import canvas
from reportlab.lib.pagesizes import letter, A4
from reportlab.lib.units import inch, cm

def create_letter_pdf(filename):
c = canvas.Canvas(filename, pagesize=letter)
width, height = letter

# Position text relative to page dimensions
c.drawString(1 * inch, height - 1 * inch, "Top of the page")
c.drawString(1 * inch, 1 * inch, "Bottom of the page")

c.save()

create_letter_pdf("letter_size.pdf")

Styling Text with Fonts

Control font family, size, and color:

from reportlab.pdfgen import canvas
from reportlab.lib.pagesizes import letter
from reportlab.lib.colors import navy, red, black

def create_styled_pdf(filename):
c = canvas.Canvas(filename, pagesize=letter)
width, height = letter

# Title with large font
c.setFont("Helvetica-Bold", 24)
c.setFillColor(navy)
c.drawString(100, height - 100, "Monthly Report")

# Subtitle
c.setFont("Helvetica", 14)
c.setFillColor(black)
c.drawString(100, height - 130, "Generated automatically")

# Body text
c.setFont("Times-Roman", 12)
c.drawString(100, height - 180, "This is the report content.")

c.save()

create_styled_pdf("styled.pdf")
tip

ReportLab includes standard fonts: Helvetica, Times-Roman, Courier, and their bold/italic variants. For custom fonts, use pdfmetrics.registerFont().

Multiline Text with TextObject

For paragraphs and multiline content, use beginText():

from reportlab.pdfgen import canvas
from reportlab.lib.pagesizes import letter

def create_multiline_pdf(filename):
c = canvas.Canvas(filename, pagesize=letter)
width, height = letter

text = c.beginText(50, height - 50)
text.setFont("Helvetica", 12)
text.setLeading(16) # Line spacing

paragraphs = [
"Executive Summary",
"",
"This report covers the quarterly performance metrics.",
"Key findings include a 15% increase in efficiency.",
"Recommendations are provided in the final section.",
]

for line in paragraphs:
text.textLine(line)

c.drawText(text)
c.save()

create_multiline_pdf("multiline.pdf")

Drawing Shapes

Add rectangles, circles, and other shapes:

from reportlab.pdfgen import canvas
from reportlab.lib.pagesizes import letter
from reportlab.lib.colors import lightblue, darkblue

def create_shapes_pdf(filename):
c = canvas.Canvas(filename, pagesize=letter)

# Filled rectangle
c.setFillColor(lightblue)
c.rect(100, 600, 200, 100, fill=True, stroke=True)

# Circle
c.setFillColor(darkblue)
c.circle(450, 650, 50, fill=True)

# Line with custom width
c.setStrokeColor(darkblue)
c.setLineWidth(3)
c.line(100, 550, 500, 550)

c.save()

create_shapes_pdf("shapes.pdf")

Adding Images

Include images in your PDF:

from reportlab.pdfgen import canvas
from reportlab.lib.pagesizes import letter
from reportlab.lib.units import inch

def create_pdf_with_image(filename, image_path):
c = canvas.Canvas(filename, pagesize=letter)
width, height = letter

# Draw image at position with specific dimensions
c.drawImage(
image_path,
x=100,
y=height - 300,
width=3 * inch,
height=2 * inch,
preserveAspectRatio=True
)

c.drawString(100, height - 320, "Figure 1: Sample Image")

c.save()

Creating Tables with PLATYPUS

For complex layouts with tables and flowing content, use the PLATYPUS engine:

from reportlab.lib import colors
from reportlab.lib.pagesizes import letter
from reportlab.platypus import SimpleDocTemplate, Table, TableStyle, Paragraph
from reportlab.lib.styles import getSampleStyleSheet

def create_table_pdf(filename):
doc = SimpleDocTemplate(filename, pagesize=letter)
elements = []
styles = getSampleStyleSheet()

# Add title
title = Paragraph("Sales Report", styles['Heading1'])
elements.append(title)

# Table data
data = [
['Product', 'Q1', 'Q2', 'Q3', 'Q4'],
['Widget A', '100', '150', '200', '180'],
['Widget B', '80', '90', '110', '130'],
['Widget C', '200', '210', '190', '220'],
]

# Create table
table = Table(data)
table.setStyle(TableStyle([
('BACKGROUND', (0, 0), (-1, 0), colors.darkblue),
('TEXTCOLOR', (0, 0), (-1, 0), colors.whitesmoke),
('ALIGN', (0, 0), (-1, -1), 'CENTER'),
('FONTNAME', (0, 0), (-1, 0), 'Helvetica-Bold'),
('FONTSIZE', (0, 0), (-1, 0), 12),
('BOTTOMPADDING', (0, 0), (-1, 0), 12),
('BACKGROUND', (0, 1), (-1, -1), colors.beige),
('GRID', (0, 0), (-1, -1), 1, colors.black),
]))

elements.append(table)
doc.build(elements)

create_table_pdf("table_report.pdf")

Multi-Page Documents

Handle automatic page breaks with PLATYPUS:

from reportlab.lib.pagesizes import letter
from reportlab.platypus import SimpleDocTemplate, Paragraph, Spacer, PageBreak
from reportlab.lib.styles import getSampleStyleSheet
from reportlab.lib.units import inch

def create_multipage_pdf(filename):
doc = SimpleDocTemplate(filename, pagesize=letter)
styles = getSampleStyleSheet()
elements = []

for i in range(1, 4):
elements.append(Paragraph(f"Chapter {i}", styles['Heading1']))
elements.append(Spacer(1, 0.25 * inch))

content = "Lorem ipsum dolor sit amet. " * 50
elements.append(Paragraph(content, styles['Normal']))

if i < 3:
elements.append(PageBreak())

doc.build(elements)

create_multipage_pdf("multipage.pdf")
note

PLATYPUS (Page Layout and Typography Using Scripts) automatically handles page breaks, text flow, and complex layouts that would be tedious to manage with the Canvas API.

Practical Example: Invoice Generator

A complete invoice generation example:

from reportlab.lib.pagesizes import letter
from reportlab.lib import colors
from reportlab.lib.units import inch
from reportlab.platypus import SimpleDocTemplate, Table, TableStyle, Paragraph, Spacer
from reportlab.lib.styles import getSampleStyleSheet, ParagraphStyle

def generate_invoice(filename, invoice_data):
doc = SimpleDocTemplate(filename, pagesize=letter)
styles = getSampleStyleSheet()
elements = []

# Header
elements.append(Paragraph("INVOICE", styles['Heading1']))
elements.append(Paragraph(f"Invoice #: {invoice_data['number']}", styles['Normal']))
elements.append(Paragraph(f"Date: {invoice_data['date']}", styles['Normal']))
elements.append(Spacer(1, 0.5 * inch))

# Items table
table_data = [['Description', 'Quantity', 'Unit Price', 'Total']]

for item in invoice_data['items']:
total = item['quantity'] * item['price']
table_data.append([
item['description'],
str(item['quantity']),
f"${item['price']:.2f}",
f"${total:.2f}"
])

# Add total row
grand_total = sum(i['quantity'] * i['price'] for i in invoice_data['items'])
table_data.append(['', '', 'Total:', f"${grand_total:.2f}"])

table = Table(table_data, colWidths=[3*inch, 1*inch, 1*inch, 1*inch])
table.setStyle(TableStyle([
('BACKGROUND', (0, 0), (-1, 0), colors.grey),
('TEXTCOLOR', (0, 0), (-1, 0), colors.whitesmoke),
('ALIGN', (1, 0), (-1, -1), 'RIGHT'),
('GRID', (0, 0), (-1, -2), 0.5, colors.grey),
('FONTNAME', (-2, -1), (-1, -1), 'Helvetica-Bold'),
]))

elements.append(table)
doc.build(elements)

# Usage
invoice = {
'number': 'INV-2026-001',
'date': '2026-01-15',
'items': [
{'description': 'Web Development', 'quantity': 10, 'price': 150.00},
{'description': 'Design Services', 'quantity': 5, 'price': 100.00},
]
}

generate_invoice("invoice.pdf", invoice)

Method Comparison

ApproachUse CaseComplexity
CanvasSimple graphics, precise positioningLow
TextObjectMultiline text blocksLow
PLATYPUSTables, flowing content, multi-pageMedium
TemplatesRepeating layouts, headers/footersHigh
warning

For very large documents or high-volume PDF generation, consider performance optimization through buffered writing or asynchronous generation.

Use the Canvas API for simple layouts with precise control, and PLATYPUS for complex documents requiring tables, automatic pagination, and flowing text.