Add Page Numbers and Headers to PDFs with Python
You've received a 50-page report from a vendor, but it has no page numbers. Before distributing it to your team or using it in a meeting, you need to add "Page X of Y" to every page. Manually editing each page isn't practical.
The traditional approach would be to use a PDF library to recreate the entire document with page numbers added. But that's complex, and you risk breaking the original formatting.
A simpler approach: create a page number template as a PDF and overlay it on top of your document. The aPDF.io Overlay API lets you layer one PDF on top of another, preserving the original content while adding your numbers.
The Workflow
- Create a page number template: A PDF with just "Page 1" positioned where you want it.
- Duplicate for each page: Generate templates for all pages ("Page 1", "Page 2", etc.).
- Overlay onto original: Layer each template onto the corresponding page of your document.
Let's build this step by step.
Step 1: Create Page Number PDFs
First, we'll use the aPDF.io Create API to generate a PDF for each page number:
import requests
API_TOKEN = 'YOUR_API_TOKEN'
CREATE_URL = 'https://apdf.io/api/pdf/file/create'
MERGE_URL = 'https://apdf.io/api/pdf/file/merge'
OVERLAY_URL = 'https://apdf.io/api/pdf/page/overlay'
def create_page_number_pdf(page_num, total_pages):
"""Generate a single-page PDF with a page number in the footer."""
html = f"""
<!DOCTYPE html>
<html>
<head>
<style>
@page {{
margin: 0;
size: A4;
}}
body {{
margin: 0;
padding: 0;
height: 297mm;
width: 210mm;
position: relative;
font-family: Arial, sans-serif;
}}
.page-number {{
position: absolute;
bottom: 15mm;
left: 0;
right: 0;
text-align: center;
font-size: 10pt;
color: #666;
}}
</style>
</head>
<body>
<div class="page-number">Page {page_num} of {total_pages}</div>
</body>
</html>
"""
response = requests.post(
CREATE_URL,
headers={
'Authorization': f'Bearer {API_TOKEN}',
'Content-Type': 'application/json',
'Accept': 'application/json'
},
json={
'html': html,
'format': 'a4',
'margin_top': '0',
'margin_bottom': '0',
'margin_left': '0',
'margin_right': '0'
}
)
return response.json()['file']
# Example: Create page number PDF for page 1 of 10
url = create_page_number_pdf(1, 10)
print(f"Page number PDF: {url}")
Step 2: Merge Page Numbers into One PDF
Now we combine all page number PDFs into a single overlay document:
def create_page_numbers_overlay(total_pages):
"""Create a multi-page PDF with all page numbers."""
print(f"Creating page numbers for {total_pages} pages...")
# Generate individual page number PDFs
page_number_urls = []
for i in range(1, total_pages + 1):
print(f" Creating page {i}/{total_pages}...")
url = create_page_number_pdf(i, total_pages)
page_number_urls.append(url)
# Merge all page number PDFs into one
print("Merging page numbers...")
files = [{'file': url} for url in page_number_urls]
response = requests.post(
MERGE_URL,
headers={
'Authorization': f'Bearer {API_TOKEN}',
'Content-Type': 'application/json',
'Accept': 'application/json'
},
json={'files': files}
)
overlay_url = response.json()['file']
print(f"Page numbers overlay ready: {overlay_url}")
return overlay_url
Step 3: Apply Overlay to Your Document
Finally, overlay the page numbers onto your original document:
def add_page_numbers(document_url, total_pages):
"""Add page numbers to an existing PDF document."""
# Step 1: Create the page numbers overlay
overlay_url = create_page_numbers_overlay(total_pages)
# Step 2: Apply overlay to original document
print("Applying page numbers to document...")
response = requests.post(
OVERLAY_URL,
headers={
'Authorization': f'Bearer {API_TOKEN}',
'Content-Type': 'application/json',
'Accept': 'application/json'
},
json={
'file': document_url,
'overlay': overlay_url
}
)
result = response.json()
print(f"Done! Document with page numbers: {result['file']}")
return result['file']
# Add page numbers to a 10-page document
document_url = 'https://your-storage.com/report-without-page-numbers.pdf'
result = add_page_numbers(document_url, 10)
Complete Script
Here's the full working script that ties everything together:
import requests
import time
API_TOKEN = 'YOUR_API_TOKEN'
CREATE_URL = 'https://apdf.io/api/pdf/file/create'
MERGE_URL = 'https://apdf.io/api/pdf/file/merge'
OVERLAY_URL = 'https://apdf.io/api/pdf/page/overlay'
METADATA_URL = 'https://apdf.io/api/pdf/metadata/read'
def get_page_count(pdf_url):
"""Get the number of pages in a PDF."""
response = requests.post(
METADATA_URL,
headers={
'Authorization': f'Bearer {API_TOKEN}',
'Content-Type': 'application/json',
'Accept': 'application/json'
},
json={'file': pdf_url}
)
return response.json()['pages']
def create_page_number_pdf(page_num, total_pages):
"""Generate a single-page PDF with a page number."""
html = f"""
<!DOCTYPE html>
<html>
<head>
<style>
@page {{ margin: 0; size: A4; }}
body {{
margin: 0; height: 297mm; width: 210mm;
position: relative; font-family: Arial, sans-serif;
}}
.page-number {{
position: absolute; bottom: 15mm;
left: 0; right: 0; text-align: center;
font-size: 10pt; color: #666;
}}
</style>
</head>
<body>
<div class="page-number">Page {page_num} of {total_pages}</div>
</body>
</html>
"""
response = requests.post(
CREATE_URL,
headers={
'Authorization': f'Bearer {API_TOKEN}',
'Content-Type': 'application/json',
'Accept': 'application/json'
},
json={'html': html, 'format': 'a4', 'margin_top': '0',
'margin_bottom': '0', 'margin_left': '0', 'margin_right': '0'}
)
return response.json()['file']
def add_page_numbers_to_pdf(document_url):
"""Main function: Add page numbers to any PDF."""
# Get page count
print("Getting document info...")
total_pages = get_page_count(document_url)
print(f"Document has {total_pages} pages")
# Create page number overlays
print("Generating page numbers...")
page_urls = []
for i in range(1, total_pages + 1):
url = create_page_number_pdf(i, total_pages)
page_urls.append(url)
print(f" Page {i}/{total_pages} created")
time.sleep(0.3) # Rate limiting
# Merge into single overlay
print("Merging page numbers...")
merge_response = requests.post(
MERGE_URL,
headers={
'Authorization': f'Bearer {API_TOKEN}',
'Content-Type': 'application/json',
'Accept': 'application/json'
},
json={'files': [{'file': url} for url in page_urls]}
)
overlay_url = merge_response.json()['file']
# Apply overlay
print("Applying to document...")
overlay_response = requests.post(
OVERLAY_URL,
headers={
'Authorization': f'Bearer {API_TOKEN}',
'Content-Type': 'application/json',
'Accept': 'application/json'
},
json={'file': document_url, 'overlay': overlay_url}
)
result_url = overlay_response.json()['file']
print(f"\\nDone! Download: {result_url}")
return result_url
# Usage
document = 'https://ontheline.trincoll.edu/images/bookdown/sample-local-pdf.pdf'
result = add_page_numbers_to_pdf(document)
Customizing the Page Number Style
You can easily customize the position and style of page numbers by modifying the HTML template:
# Top-right corner with different styling
html = f"""
<style>
.page-number {{
position: absolute;
top: 10mm;
right: 15mm;
font-size: 9pt;
color: #333;
font-weight: bold;
}}
</style>
<body>
<div class="page-number">{page_num} / {total_pages}</div>
</body>
"""
# Bottom-left with company name
html = f"""
<style>
.footer {{
position: absolute;
bottom: 10mm;
left: 15mm;
right: 15mm;
display: flex;
justify-content: space-between;
font-size: 8pt;
color: #999;
}}
</style>
<body>
<div class="footer">
<span>Confidential - Acme Corp</span>
<span>Page {page_num} of {total_pages}</span>
</div>
</body>
"""
Next Steps
Now that you can add page numbers, explore related features:
- Add watermarks: Use the same Overlay endpoint to add "DRAFT" or "CONFIDENTIAL" watermarks.
- Add letterhead: Use the Underlay endpoint to add company letterhead behind the content.