Build Modern, Print-Ready PDFs with Python, Flask & WeasyPrint

Saurav Jaiswal

  1. Jun 30, 2025
  2. 4 min read

PDF generation is a common requirement in modern web applications—especially for reports, invoices, purchase orders, and official documentation. One of the most powerful tools for Python developers is WeasyPrint, which allows you to convert HTML + CSS into PDFs with high-quality rendering.

This blog covers:

  • What is WeasyPrint?
  • Stylish theme ideas for PDFs
  • Flask API implementation
  • Testing the endpoint
  • Tips for customization

What is WeasyPrint?

WeasyPrint is a visual rendering engine for HTML and CSS that can output to PDF. It is designed to support modern CSS (like Flexbox, Grid, media queries) and is widely used for document generation.

Think of it as a "headless browser" that prints HTML/CSS to PDF with beautiful accuracy.

Why Use WeasyPrint?

  • Supports modern CSS (Fonts, Flexbox, Grid)
  • Works with templates like Jinja2
  • Clean API and command-line support
  • Supports page breaks, headers/footers, and inline images
  • Ideal for invoices, reports, forms, etc.

Step-by-Step Installation Commands


pip install Flask WeasyPrint
For Ubuntu:
sudo apt update
sudo apt install libpango-1.0-0 libpangocairo-1.0-0 libcairo2 libffi-dev shared-mime-info
​​For macOS (using Homebrew):
brew install cairo pango gdk-pixbuf libffi

Stylish Themes for PDF Design

To make PDFs not only functional but also beautiful, you can apply CSS themes just like on a webpage. Below are a few example design themes:

templates/pdf_template.html

Attractive Modern UI -


<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>{{ title }}</title>
  <link rel="stylesheet" href="{{ url_for('static', filename='style.css') }}">
</head>
<body>
  <div class="document">

    <header class="header">
      <div class="logo">
        <h1>{{ heading }}</h1>
        <p>{{ description }}</p>
      </div>
    </header>

    <section class="info-box">
      <h2>Report Summary</h2>
      <p><strong>Date:</strong> {{ date or "June 26, 2025" }}</p>
      <p><strong>Prepared By:</strong> {{ prepared_by or "System Admin" }}</p>
    </section>

    <section class="table-section">
      <h2>Purchase Items</h2>
      <table class="styled-table">
        <thead>
          <tr>
            <th>Item</th>
            <th>Quantity</th>
            <th>Rate (INR)</th>
            <th>Total</th>
          </tr>
        </thead>
        <tbody>
          {% for item in items %}
            <tr>
              <td>{{ item.name }}</td>
              <td>{{ item.qty }}</td>
              <td>{{ item.rate }}</td>
              <td>{{ item.qty * item.rate }}</td>
            </tr>
          {% endfor %}
        </tbody>
      </table>
    </section>

    <footer class="footer">
      <p>Generated by MySystem | &copy; {{ year or "2025" }}</p>
    </footer>

  </div>
</body>
</html>

<style>
body {
  font-family: 'Segoe UI', sans-serif;
  color: #333;
  background: #f7f9fc;
  margin: 0;
  padding: 0;
}

.document {
  padding: 2rem;
  background: white;
  max-width: 800px;
  margin: auto;
  box-shadow: 0 0 20px rgba(0,0,0,0.1);
  border-radius: 10px;
}

.header {
  background-color: #4a90e2;
  color: white;
  padding: 2rem;
  border-radius: 10px 10px 0 0;
  text-align: center;
}

.header h1 {
  margin: 0;
  font-size: 2rem;
}

.info-box {
  background: #eef3f7;
  border-left: 5px solid #4a90e2;
  padding: 1rem 1.5rem;
  margin-top: 1.5rem;
  border-radius: 5px;
}

.info-box h2 {
  margin-top: 0;
  color: #4a4a4a;
}

.table-section {
  margin-top: 2rem;
}

.table-section h2 {
  border-bottom: 2px solid #ddd;
  padding-bottom: 0.5rem;
  margin-bottom: 1rem;
  color: #2c3e50;
}

.styled-table {
  width: 100%;
  border-collapse: collapse;
}

.styled-table thead {
  background-color: #e6f0ff;
  color: #333;
}

.styled-table th,
.styled-table td {
  padding: 12px 15px;
  border: 1px solid #ccc;
}

.styled-table tr:nth-child(even) {
  background-color: #f2f6fc;
}

.footer {
  margin-top: 3rem;
  text-align: center;
  font-size: 0.9rem;
  color: #999;
  border-top: 1px solid #ccc;
  padding-top: 1rem;
}
</style>

Flask + WeasyPrint PDF Generator API

Here’s how you can implement a /api/generate-pdf endpoint using Flask and WeasyPrint.

Folder Structure


project/
 │
├── app.py
├── templates/
 │   └── pdf_template.html


app.py

from flask import Flask, request, render_template, send_file, url_for
from weasyprint import HTML
import tempfile
import os

app = Flask(__name__)

@app.route("/api/generate-pdf", methods=["POST"])
def generate_pdf():
    data = request.json

    # Render HTML with data
    rendered_html = render_template("pdf_template.html",
                                    title=data.get("title", "PDF Document"),
                                    heading=data.get("heading", "PDF Generated"),
                                    description=data.get("description", ""),
                                    items=data.get("items", []))

    # Generate temporary file path
    with tempfile.NamedTemporaryFile(delete=False, suffix=".pdf") as tmp:
        pdf_path = tmp.name

    # Generate PDF
    HTML(string=rendered_html, base_url=request.base_url).write_pdf(pdf_path)

    return send_file(pdf_path, as_attachment=True, download_name="document.pdf")

Example Request (Postman or JS/Fetch)


POST /api/generate-pdf
Content-Type: application/json

{
    "title": "Monthly Purchase Report",
    "heading": "Report - June 2025",
    "description": "This report contains the list of purchases for the month of June.",
    "items": [
        { "name": "Laptop", "qty": 2, "rate": 55000 },
        { "name": "Keyboard", "qty": 5, "rate": 1200 }
    ]
}

Tips for Better PDF Styling

  • Use Google Fonts with @import in your CSS
  • Include company logos or QR codes with <img> tags and relative paths
  • Use media queries (@media print) to hide or show certain elements
  • Add page numbers using @page and counter(page)
  • For better print quality, always use high-resolution images

@page {
    size: A4;
    margin: 1in;
}

Common Issues & Solutions

  • Images not showing
    Solution: Use base_url when rendering HTML (e.g., with WeasyPrint or Flask renderers)
  • CSS not applied
    Solution: Use url_for('static', filename='path/to/file.css') in templates
  • Broken fonts
    Solution: Use system fonts (like Arial, Times) or host fonts locally (avoid external CDNs)
  • Header/Footer not repeating on every page
    Solution: Use position: running(header) in CSS and define headers/footers using @page rules

Conclusion

With WeasyPrint and Flask, you can create stylish, responsive, and professional PDF documents just like a web page. Thanks to its HTML + CSS-based design, your frontend team can collaborate with backend developers to make the perfect document experience.

About Author
Saurav Jaiswal

See What Our Clients Say

Mindgap

Incentius has been a fantastic partner for us. Their strong expertise in technology helped deliver some complex solutions for our customers within challenging timelines. Specific call out to Sujeet and his team who developed custom sales analytics dashboards in SFDC for a SoCal based healthcare diagnostics client of ours. Their professionalism, expertise, and flexibility to adjust to client needs were greatly appreciated. MindGap is excited to continue to work with Incentius and add value to our customers.

Samik Banerjee

Founder & CEO

World at Work

Having worked so closely for half a year on our website project, I wanted to thank Incentius for all your fantastic work and efforts that helped us deliver a truly valuable experience to our WorldatWork members. I am in awe of the skills, passion, patience, and above all, the ownership that you brought to this project every day! I do not say this lightly, but we would not have been able to deliver a flawless product, but for you. I am sure you'll help many organizations and projects as your skills and professionalism are truly amazing.

Shantanu Bayaskar

Senior Project Manager

Gogla

It was a pleasure working with Incentius to build a data collection platform for the off-grid solar sector in India. It is rare to find a team with a combination of good understanding of business as well as great technological know-how. Incentius team has this perfect combination, especially their technical expertise is much appreciated. We had a fantastic time working with their expert team, especially with Amit.

Viraj gada

Gogla

Humblx

Choosing Incentius to work with is one of the decisions we are extremely happy with. It's been a pleasure working with their team. They have been tremendously helpful and efficient through the intense development cycle that we went through recently. The team at Incentius is truly agile and open to a discussion in regards to making tweaks and adding features that may add value to the overall solution. We found them willing to go the extra mile for us and it felt like working with someone who rooted for us to win.

Samir Dayal Singh

CEO Humblx

Transportation & Logistics Consulting Organization

Incentius is very flexible and accommodating to our specific needs as an organization. In a world where approaches and strategies are constantly changing, it is invaluable to have an outsourcer who is able to adjust quickly to shifts in the business environment.

Transportation & Logistics Consulting Organization

Consultant

Mudraksh & McShaw

Incentius was instrumental in bringing the visualization aspect into our investment and trading business. They helped us organize our trading algorithms processing framework, review our backtests and analyze results in an efficient, visual manner.

Priyank Dutt Dwivedi

Mudraksh & McShaw Advisory

Leading Healthcare Consulting Organization

The Incentius resource was highly motivated and developed a complex forecasting model with minimal supervision. He was thorough with quality checks and kept on top of multiple changes.

Leading Healthcare Consulting Organization

Sr. Principal

US Fortune 100 Telecommunications Company

The Incentius resource was highly motivated and developed a complex forecasting model with minimal supervision. He was thorough with quality checks and kept on top of multiple changes.

Incentive Compensation

Sr. Director

Most Read
Real-Time Data Streaming with Python Flask and Quasar Framework

Implementing real-time data streaming from a server to a client can be challenging, especially when working with APIs that return data in chunks. Let me share a story of how I tackled this problem while using Python Flask for the backend and Vue.js with the Quasar framework for the frontend. It was a journey filled with trials, errors, and some exciting discoveries.

Yash Pukale

  1. Dec 23, 2024
  2. 4 min read
A Fresh Take on Agentic AI: Transforming How We Work and Innovate

Agentic AI is quickly becoming a buzzword in the world of technology, and for good reason. Imagine AI agents capable of thinking, planning, and executing tasks with minimal human input—this is the promise of Agentic AI. It’s a revolutionary step forward, allowing businesses to operate smarter, faster, and more efficiently.

Chetan Patel

  1. Dec 19, 2024
  2. 4 min read
Simplifying Data Workloads: Amazon S3 Tables and Apache Iceberg

In the world of big data, efficient management and analysis of large datasets is crucial. Amazon S3 Tables offer a fully managed solution built on Apache Iceberg, a modern table format designed to handle massive-scale analytical workloads with precision and efficiency.

Mayur Patel

  1. Dec 11, 2024
  2. 4 min read
Revolutionizing Business with Data Analytics and AI in 2025

How can businesses identify untapped opportunities, improve efficiency, and design more effective marketing campaigns? The answer lies in leveraging the power of data. Today, data analytics isn’t just a support function—it’s the backbone of decision-making. When combined with Artificial Intelligence (AI), it transforms how companies operate, enabling them to predict trends, optimize operations, and deliver better customer experiences.

Marketing

  1. Dec 04, 2024
  2. 4 min read