Skip to content

How to Create PDF Documents in Python with ReportLab

Learn to generate invoices, reports, and certificates programmatically in Python using ReportLab's Canvas API and PLATYPUS for precise text, shapes, tables, and multi-page layouts.

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)
    
    c.drawString(100, 750, "Hello World from Python!")
    
    c.line(100, 740, 400, 740)
    
    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
    
    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
    
    c.setFont("Helvetica-Bold", 24)
    c.setFillColor(navy)
    c.drawString(100, height - 100, "Monthly Report")
    
    c.setFont("Helvetica", 14)
    c.setFillColor(black)
    c.drawString(100, height - 130, "Generated automatically")
    
    c.setFont("Times-Roman", 12)
    c.drawString(100, height - 180, "This is the report content.")
    
    c.save()

create_styled_pdf("styled.pdf")

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)
    
    c.setFillColor(lightblue)
    c.rect(100, 600, 200, 100, fill=True, stroke=True)
    
    c.setFillColor(darkblue)
    c.circle(450, 650, 50, fill=True)
    
    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
    
    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()
    
    title = Paragraph("Sales Report", styles['Heading1'])
    elements.append(title)
    
    data = [
        ['Product', 'Q1', 'Q2', 'Q3', 'Q4'],
        ['Widget A', '100', '150', '200', '180'],
        ['Widget B', '80', '90', '110', '130'],
        ['Widget C', '200', '210', '190', '220'],
    ]
    
    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")

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 = []
    
    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))
    
    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}"
        ])
    
    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)

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

Approach Use Case Complexity
Canvas Simple graphics, precise positioning Low
TextObject Multiline text blocks Low
PLATYPUS Tables, flowing content, multi-page Medium
Templates Repeating layouts, headers/footers High

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

Navigation