Skip to main content

Generate PDFs in Python with Pyppeteer: Full Guide

· 9 min read
Michał Szymanowski
Michał Szymanowski
PDFBolt Co-Founder

Illustration of generating PDF files using Pyppeteer

This tutorial walks through generating a certificate PDF using Pyppeteer and Mako templates in Python. We'll set up the project, design an HTML certificate template, populate it with data, and convert it to PDF using Pyppeteer's headless browser.

What is Pyppeteer?

Pyppeteer is a Python port of Puppeteer that provides a high-level API to control Chromium in headless mode. It can render dynamic HTML, run JavaScript, take screenshots, and convert HTML to PDF. If you're already familiar with Puppeteer in Node.js, Pyppeteer works the same way but with a Pythonic API.

Differences Between Puppeteer and Pyppeteer

Pyppeteer vs Puppeteer comparison for Python PDF generation

While this guide focuses on using Pyppeteer to generate certificates in Python, it's helpful to understand how it differs from its JavaScript counterpart, Puppeteer. For the Node.js version, see our Puppeteer PDF generation guide. Below are some key distinctions.

Language and API Design

  • Puppeteer is built for Node.js and uses JavaScript’s asynchronous model with Promises.
  • Pyppeteer adapts this functionality for Python, offering a more Pythonic API that accepts both dictionaries and keyword arguments, making it more intuitive for Python developers.

Options Passing

  • Puppeteer: Uses JavaScript objects (e.g., launch({ headless: true })).
  • Pyppeteer: Supports both dictionaries and keyword arguments (e.g., launch(headless=True)).

Element Selection

  • Puppeteer: Employs concise methods like $(), $$(), and $x().
  • Pyppeteer: Uses more descriptive methods (querySelector(), querySelectorAll(), and xpath()) along with shorthands (J(), JJ(), and Jx()).

JavaScript Evaluation

  • Both libraries use evaluate(), but Pyppeteer may need force_expr=True to correctly interpret expressions.

In practice, if you know Puppeteer, picking up Pyppeteer is straightforward.

Step-by-Step Guide: Creating a Certificate PDF with Pyppeteer

We'll use Mako for templating and Pyppeteer for PDF rendering.

Step 1: Set Up Your Environment

Prerequisites: Make sure you have Python and pip installed before starting.

RequirementRecommendation and Download Links
PythonPython 3.6 or higher installed. If not, download from Python.org.
Package ManagerUse pip (included with Python) or pipenv for dependency management. Learn more in the pip documentation.
IDEUse PyCharm – my personal recommendation – or VS Code.
  1. Then create a new project directory and navigate into it:
mkdir certificate-generation
cd certificate-generation
  1. Install the necessary packages.

Use pip to install Pyppeteer (for generating PDFs) and Mako (for templating):

pip install pyppeteer mako

Step 2: Set Up Your Project Directory Structure

Organize your project files like this:

Recommended layout:

certificate-generation/
├── data/ # Directory for your JSON data files
│ └── certificate_data.json # File containing certificate details
├── templates/ # Directory for your HTML templates
│ └── certificate_template.html # Mako template for the certificate design
└── generate_certificate.py # Python script for generating the PDF

Step 3: Create Your Certificate Template with Mako

  • Design your certificate using HTML and CSS, and use Mako templating syntax to dynamically insert data.
  • Save the template as certificate_template.html in your templates directory.
  • This template should include placeholders that will be dynamically populated with your certificate data.
View Certificate Template (HTML)
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8"/>
<title>Certificate</title>
<style>
body, html {
margin: 0;
padding: 0;
}

body {
font-family: Georgia, serif;
color: #000;
display: flex;
justify-content: center;
align-items: center;
}

.container {
width: 100%;
aspect-ratio: 1050 / 742;
border: 20px solid #c9aa81;
padding: 30px;
box-sizing: border-box;
display: flex;
flex-direction: column;
background-image: linear-gradient(135deg, #fdfcfb 0%, #e2d1c3 100%);
}

.header {
color: #b59467;
margin: 50px 0 30px;
text-align: center;
}

.title {
font-size: 78px;
letter-spacing: 4px;
margin: 0;
text-shadow: 2px 2px 4px rgba(0, 0, 0, 0.1);
}

.subtitle {
font-size: 30px;
letter-spacing: 2px;
margin-top: 5px;
}

.person-description {
text-align: center;
font-size: 24px;
margin: 10px 0;
}

.person {
font-size: 62px;
font-style: italic;
margin: 20px auto;
text-align: center;
color: #333;
}

.course {
text-align: center;
margin: 20px 0;
}

.course-description {
font-size: 24px;
margin-bottom: 20px;
}

.course-name {
font-size: 30px;
color: #333;
}

.footer-details {
display: flex;
justify-content: space-between;
margin-top: auto;
padding-top: 15px;
}

.footer-item {
display: flex;
flex-direction: column;
align-items: center;
flex: 1;
padding: 10px;
}

.footer-item .label {
font-size: 18px;
text-align: center;
margin-top: 5px;
}

.value {
font-size: 24px;
font-family: 'Satisfy', cursive;
font-style: italic;
}

.divider {
width: 60%;
border: 0;
border-top: 1px solid #7c5f38;
margin: 10px auto;
}

@media (max-width: 600px) {
.title {
font-size: 36px;
}

.person {
font-size: 24px;
}

.person-description,
.course-description {
font-size: 18px;
}

.course-name {
font-size: 28px;
}
}
</style>
</head>
<body>
<div class="container">
<div class="header">
<h1 class="title">Certificate</h1>
<h2 class="subtitle">of Completion</h2>
</div>
<div class="person-description">This certificate is proudly presented to</div>
<div class="person">${recipient_name}</div>
<div class="course">
<div class="course-description">
For successfully completing the course:
</div>
<div class="course-name">${course_name}</div>
</div>
<div class="footer-details">
<div class="footer-item">
<div class="value">${date}</div>
<hr class="divider">
<div class="label">Date</div>
</div>
<div class="footer-item">
<div class="value">${instructor}</div>
<hr class="divider">
<div class="label">Instructor</div>
</div>
</div>
</div>
</body>
</html>

Step 4: Prepare Your Certificate Data

Next, create a JSON file named certificate_data.json in your data directory with the dynamic values for your certificate.

View Certificate Data (JSON)
{
"recipient_name": "Jane Doe",
"course_name": "Digital Marketing Strategies",
"date": "2025-03-09",
"instructor": "John Smith"
}

Step 5: Write the PDF Generation Script

Now, create a Python script called generate_certificate.py in the root of your project. This script will load the certificate data, render the HTML using the Mako template, and generate a PDF using Pyppeteer.

View Complete Script
import os
import json
import asyncio
from mako.template import Template
import datetime
import random

# Set the desired Chromium revision for pyppeteer
PYPPETEER_CHROMIUM_REVISION = '1263111'
os.environ['PYPPETEER_CHROMIUM_REVISION'] = PYPPETEER_CHROMIUM_REVISION

from pyppeteer import launch

async def generate_pdf(html_content, output_path):
# Launch a headless Chromium browser
browser = await launch()
page = await browser.newPage()

# Set the HTML content for the page and wait for all resources to load
await page.setContent(html_content)

# Generate the PDF in A4 landscape mode with background graphics printed
await page.pdf({
'path': output_path,
'format': 'A4',
'printBackground': True,
'landscape': True
})

# Close the browser
await browser.close()


def render_template(template_path, data):
# Load the Mako template from file
with open(template_path, 'r', encoding='utf-8') as file:
template_content = file.read()
template = Template(template_content)
# Render the template with provided data
return template.render(**data)


def load_data(json_path):
# Load data from a JSON file
with open(json_path, 'r', encoding='utf-8') as file:
data = json.load(file)
return data


async def main():
# Define file paths
json_path = os.path.join('data', 'certificate_data.json')
template_path = os.path.join('templates', 'certificate_template.html')

# Create a unique output file name using a timestamp and a random number
timestamp = datetime.datetime.now().strftime("%Y%m%d%H%M%S")
output_pdf = f'certificate_{timestamp}.pdf'

# Load certificate data and render the HTML template
data = load_data(json_path)
rendered_html = render_template(template_path, data)

# Generate the PDF
await generate_pdf(rendered_html, output_pdf)
print(f'PDF generated: {output_pdf}')


if __name__ == '__main__':
asyncio.run(main())

Step 6: Run the Script

With everything in place, navigate to your certificate-generation directory and run your script from the terminal:

python generate_certificate.py

Step 7: Verify Your Certificate PDF

After running the script, open the generated certificate_<timestamp>.pdf file and check that the layout and data look correct.

Certificate PDF Preview:

Example of a generated certificate PDF using Pyppeteer

Bonus: Generating a Web Page PDF

In addition to creating PDFs from HTML, you can also generate a PDF directly from an existing web page. This is useful if you need to archive a webpage or create a snapshot of a live site.

Below is a quick example of how to do it using Pyppeteer.

View Code for Generating PDF from URL
import asyncio
import os

# Set the desired Chromium revision for pyppeteer
PYPPETEER_CHROMIUM_REVISION = '1263111'
os.environ['PYPPETEER_CHROMIUM_REVISION'] = PYPPETEER_CHROMIUM_REVISION

from pyppeteer import launch

async def generate_pdf_from_webpage(target_url, output_file):
# Initialize a headless Chromium browser instance
browser = await launch()
page = await browser.newPage()

# Navigate to the target URL and wait until network activity calms down
await page.goto(target_url, {'waitUntil': 'networkidle2'})

# Create a PDF file in A4 format, ensuring background graphics are included
await page.pdf({
'path': output_file,
'format': 'A4',
'printBackground': True
})

# Close the browser to free up resources
await browser.close()

# Example usage: Generate a PDF snapshot of a live webpage
asyncio.run(generate_pdf_from_webpage('https://example.com', 'webpage.pdf'))

Simplified Approach: Using PDF Templates

Pyppeteer gives you full control, but you also need to manage Chromium, handle browser lifecycles, and deal with memory on your server. If you'd rather skip that, PDFBolt Templates let you design the layout once and generate PDFs via API with just JSON data – no browser dependencies on your end.

Certificate Generation via Templates

Below is the process for generating our certificate through PDFBolt's template system.

Step 1: Create Your Template

  1. Sign up or log into your account.
  2. Navigate to TemplatesCreate Template.
  3. Choose Create from Scratch or select a certificate from the gallery and customize it to your needs.
Template Resources
  1. Design your certificate using HTML/CSS with Handlebars syntax:
Certificate Template with Handlebars
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8"/>
<title>Certificate</title>
<style>
body, html {
margin: 0;
padding: 0;
}

body {
font-family: Georgia, serif;
color: #000;
display: flex;
justify-content: center;
align-items: center;
}

.container {
width: 100%;
aspect-ratio: 1050 / 742;
border: 20px solid #c9aa81;
padding: 30px;
box-sizing: border-box;
display: flex;
flex-direction: column;
background-image: linear-gradient(135deg, #fdfcfb 0%, #e2d1c3 100%);
}

.header {
color: #b59467;
margin: 50px 0 30px;
text-align: center;
}

.title {
font-size: 78px;
letter-spacing: 4px;
margin: 0;
text-shadow: 2px 2px 4px rgba(0, 0, 0, 0.1);
}

.subtitle {
font-size: 30px;
letter-spacing: 2px;
margin-top: 5px;
}

.person-description {
text-align: center;
font-size: 24px;
margin: 10px 0;
}

.person {
font-size: 62px;
font-style: italic;
margin: 20px auto;
text-align: center;
color: #333;
}

.course {
text-align: center;
margin: 20px 0;
}

.course-description {
font-size: 24px;
margin-bottom: 20px;
}

.course-name {
font-size: 30px;
color: #333;
}

.footer-details {
display: flex;
justify-content: space-between;
margin-top: auto;
padding-top: 15px;
}

.footer-item {
display: flex;
flex-direction: column;
align-items: center;
flex: 1;
padding: 10px;
}

.footer-item .label {
font-size: 18px;
text-align: center;
margin-top: 5px;
}

.value {
font-size: 24px;
font-family: 'Satisfy', cursive;
font-style: italic;
}

.divider {
width: 60%;
border: 0;
border-top: 1px solid #7c5f38;
margin: 10px auto;
}
</style>
</head>
<body>
<div class="container">
<div class="header">
<h1 class="title">Certificate</h1>
<h2 class="subtitle">of Completion</h2>
</div>
<div class="person-description">This certificate is proudly presented to</div>
<div class="person">{{recipient_name}}</div>
<div class="course">
<div class="course-description">
For successfully completing the course:
</div>
<div class="course-name">{{course_name}}</div>
</div>
<div class="footer-details">
<div class="footer-item">
<div class="value">{{date}}</div>
<hr class="divider">
<div class="label">Date</div>
</div>
<div class="footer-item">
<div class="value">{{instructor}}</div>
<hr class="divider">
<div class="label">Instructor</div>
</div>
</div>
</div>
</body>
</html>

Step 2: Test and Publish

  1. Add sample data in the DATA tab to preview your template:
{
"recipient_name": "Jane Doe",
"course_name": "Digital Marketing Strategies",
"date": "2025-03-09",
"instructor": "John Smith"
}
  1. Use Real PDF Preview to generate an actual PDF and verify the output.
  2. Click Publish to make your template available via API.
  3. Copy your template ID for integration or access ready-to-use code snippets for multiple programming languages through the Get API Code button.

Step 3: Generate Certificates from Python

Here's the equivalent Python code using PDFBolt's API – notice how much simpler it becomes:

import requests
import json

url = "https://api.pdfbolt.com/v1/direct"
headers = {
"API-KEY": "XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX",
"Content-Type": "application/json"
}

data_json = '''{
"templateId": "your-certificate-template-id",
"templateData": {
"recipient_name":"Jane Doe",
"course_name":"Digital Marketing Strategies",
"date":"2025-03-09",
"instructor":"John Smith"
}
}'''

data = json.loads(data_json)

try:
response = requests.post(url, headers=headers, json=data)
response.raise_for_status()

with open('pdfbolt_certificate_example.pdf', 'wb') as f:
f.write(response.content)
print("PDF generated successfully")

except requests.exceptions.HTTPError as e:
print(f"HTTP {response.status_code}")
print(f"Error Message: {response.text}")
except requests.exceptions.RequestException as e:
print(f"Error: {e}")

PDF Configuration Options

You can configure PDF options like format, landscape, printBackground, and other parameters in two ways:

  1. In the Template Designer: Set options in the OPTIONS tab when creating your template.
  2. In API Requests: Include parameters directly in your API request payload:
{
"templateId": "your-certificate-template-id",
"templateData": { ... },
"format": "A4",
"landscape": true,
"printBackground": true
}

That's it. To generate a certificate for a different recipient, just change the JSON data – the template stays the same.

Other conversion modes

Beyond templates, PDFBolt also supports:

Quick start with Python

See the Python quick start guide for code examples.

Conclusion

Pyppeteer with Mako templates gives you full control over PDF generation in Python – you own the rendering pipeline and can customize everything. The tradeoff is managing Chromium and its memory footprint on your server.

PDFBolt Templates remove that overhead: design your layout in the browser, send JSON data via API, get a PDF back. Less code, no browser dependencies.

Pick Pyppeteer when you need fine-grained control over the rendering. Pick templates when you'd rather focus on your application logic.

Other Python PDF guides

Looking for a different library? See our guides on WeasyPrint and PyPDF2, PDFKit, and xhtml2pdf, or browse the full Python HTML to PDF libraries comparison.