When working with PDFs in Python using ReportLab, managing page numbers is one of the most common formatting tasks. Whether you’re generating invoices, reports, eBooks, or certificates, customizing the page numbering is critical for proper organization and navigation.
By default, ReportLab starts counting pages from 1 and places page numbers wherever you manually add them in your document. But what if you want to start from page 3, or show the first page as “i”, and then continue with Arabic numerals? Or what if your document includes a cover page or a table of contents, and you want the numbering to begin only after those pages?
In this blog, we’ll explore how to change the page numbering index in ReportLab, including:
- Starting page numbering from a specific page
- Customizing the page number format
- Skipping page numbers on some pages
- Adding page numbers dynamically using canvas callbacks
What is ReportLab?
ReportLab is a powerful Python library for generating PDFs programmatically. It supports a wide range of features including text formatting, charts, images, tables, and of course, custom page layout control.
The library uses two key APIs:
- Canvas API – lower level, for absolute positioning
- Platypus (Page Layout and Typography Using Scripts) – higher level, for building documents using flows
For page numbering, you typically use a combination of canvas methods and Platypus callbacks.
Scenario: Skipping Cover Page and Starting Page Numbering from Page 2
Let’s say you are generating a PDF with the following structure:
- Cover Page – No page number
- Table of Contents – No page number
- Main Content – Page numbers start here as “Page 1”
We want to start numbering only from the third page, but have it say “Page 1”.
Step-by-Step Guide
Step 1: Setting Up Your ReportLab Environment
Install ReportLab if you haven’t already:
pip install reportlab
Start by importing required modules:
from reportlab.platypus import BaseDocTemplate, PageTemplate, Frame, Paragraph, Spacer
from reportlab.platypus import Table, TableStyle
from reportlab.lib.pagesizes import A4
from reportlab.lib.styles import getSampleStyleSheet
from reportlab.pdfgen.canvas import Canvas
Step 2: Create a Custom Page Numbering Callback
To control page numbering, we use a canvas callback function that runs for each page. Inside this function, we decide whether to show a number and what number to show.
def add_page_number(canvas, doc):
# doc.page is the actual page number in the document
# doc.page - 2 starts from 1 on the third page
if doc.page > 2:
page_number_text = f"Page {doc.page - 2}"
canvas.setFont("Helvetica", 10)
canvas.drawRightString(200 * mm, 15 * mm, page_number_text)
Here, we subtract 2 from the actual page number to start from “Page 1” on the third physical page.
Step 3: Create a Custom Page Template with Frames
A Frame defines the printable area of your content. We embed this into a PageTemplate, then pass the template to our document.
from reportlab.lib.units import mm
frame = Frame(25 * mm, 25 * mm, 160 * mm, 250 * mm, id='normal')
page_template = PageTemplate(id='custom', frames=frame, onPage=add_page_number)
Step 4: Define the Document
Create a BaseDocTemplate
and add the custom template.
doc = BaseDocTemplate("custom_numbered_output.pdf", pagesize=A4)
doc.addPageTemplates([page_template])
Step 5: Build the PDF Content
Now add some dummy content, including pages before the actual numbered ones.
styles = getSampleStyleSheet()
story = []
# Cover Page (no numbering)
story.append(Paragraph("COVER PAGE", styles['Title']))
story.append(Spacer(1, 200))
# Force new page
story.append(Spacer(1, A4[1]))
story.append(Paragraph("TABLE OF CONTENTS", styles['Title']))
story.append(Spacer(1, 200))
# Force another new page
story.append(Spacer(1, A4[1]))
story.append(Paragraph("Main Content Begins", styles['Title']))
for i in range(1, 6):
story.append(Paragraph(f"This is paragraph {i}", styles['Normal']))
story.append(Spacer(1, 12))
Now generate the PDF:
doc.build(story)
Output Explanation
- The first two pages will not display page numbers.
- On the third page, numbering begins from Page 1.
- It continues incrementally on each following page.
Alternative: Using Platypus PageCount
with Page Templates
If you prefer using SimpleDocTemplate
or PageTemplate
, you can use canvas.getPageNumber()
or custom attributes to control behavior even more precisely.
You can also add multiple templates, one for unnumbered pages and one for numbered pages.
def number_pages(canvas, doc):
if doc.page >= 3:
number = doc.page - 2
canvas.drawRightString(200 * mm, 15 * mm, f"Page {number}")
Then define multiple templates:
doc.addPageTemplates([
PageTemplate(id='FirstTwo', frames=frame), # No onPage
PageTemplate(id='Numbered', frames=frame, onPage=number_pages)
])
Then dynamically switch between them using NextPageTemplate
in your flowables:
from reportlab.platypus import NextPageTemplate, PageBreak
story.append(Paragraph("Cover Page", styles['Title']))
story.append(NextPageTemplate('FirstTwo'))
story.append(PageBreak())
story.append(Paragraph("Table of Contents", styles['Title']))
story.append(NextPageTemplate('Numbered'))
story.append(PageBreak())
story.append(Paragraph("Page Numbering Starts Here", styles['Title']))
This gives you full control over where and how numbering begins.
Custom Page Number Formats
You can customize the format too — for instance, using Roman numerals for preliminary pages and Arabic for main content.
Here’s a quick utility function:
def to_roman(num):
val = [1000, 900, 500, 400, 100, 90, 50, 40, 10, 9, 5, 4, 1]
syms = ["M", "CM", "D", "CD", "C", "XC", "L", "XL", "X", "IX", "V", "IV", "I"]
roman = ''
i = 0
while num > 0:
for _ in range(num // val[i]):
roman += syms[i]
num -= val[i]
i += 1
return roman
Then use it in the callback:
if doc.page <= 2:
page_number = to_roman(doc.page).lower()
else:
page_number = str(doc.page - 2)
canvas.drawRightString(200 * mm, 15 * mm, f"Page {page_number}")
Tips for Real-World Use
- Always preview your PDF: Page numbering can be tricky with flowable content like tables and long paragraphs.
- Use
PageBreak()
orSpacer
to control pagination. - Test different styles and positions for page numbers.
- Modularize your code: Wrap page-numbering logic into reusable functions.
- Log page indexes during build phase if debugging: you can insert
print(doc.page)
inside your canvas function.
Conclusion
Changing the page numbering index in ReportLab gives you full control over how and where numbers appear in your PDF documents. Whether you’re skipping pages, starting at a specific index, or switching formats from Roman to Arabic numerals, the flexibility of ReportLab’s canvas callbacks and page templates makes this both powerful and accessible.
With a clear structure and a few reusable functions, you can produce professionally formatted PDFs tailored to any workflow or reporting need.
Stay in touch with wwebhub.com .