Generate Professional PDFs Directly from FastAPI with xhtml2pdf
Learn how to quickly create downloadable, professionally styled PDF invoices using FastAPI and the xhtml2pdf library, covering prerequisites, FastAPI setup, HTML template design, PDF rendering, and server launch, plus tips for dynamic data, alternative tools, and security considerations.
Want to generate professionally styled PDF documents directly from a FastAPI backend? Whether it's invoices, reports, or certificates, converting HTML to downloadable PDFs is simpler than you think. This guide walks through using FastAPI and the xhtml2pdf library to create a polished invoice PDF.
Clean invoice layout designed with CSS
Dynamic HTML‑to‑PDF rendering
Downloadable response with proper headers
🛠️ Prerequisites
<code>pip install fastapi uvicorn xhtml2pdf</code>The built‑in io module is used for in‑memory file streams.
⚙️ Set up FastAPI
<code>from fastapi import FastAPI
from fastapi.responses import StreamingResponse
from xhtml2pdf import pisa
import io
app = FastAPI()</code>💅 Build HTML template
The following string contains an HTML invoice template with embedded CSS. Customize the styles or content as needed.
<code>html = """
<html>
<head>
<style>
body { font-family: Helvetica, sans-serif; padding: 40px; color: #333; }
h1, h2, h3 { color: #4CAF50; }
.header, .footer { width: 100%; text-align: center; position: fixed; }
.header { top: 0px; }
.footer { bottom: 0px; font-size: 12px; color: #888; }
.invoice-box { border: 1px solid #eee; padding: 30px; border-radius: 10px; background: #f9f9f9; }
.company-details { text-align: right; }
.customer-details { margin-top: 20px; }
table { width: 100%; border-collapse: collapse; margin-top: 30px; }
table th { background-color: #4CAF50; color: white; padding: 10px; text-align: left; }
table td { padding: 10px; border-bottom: 1px solid #ddd; }
.total { text-align: right; font-size: 16px; margin-top: 20px; }
</style>
</head>
<body>
<div class="header"><h2>Elegant Invoice</h2></div>
<div class="invoice-box">
<div class="company-details"><strong>Acme Corp</strong><br>123 Business Rd.<br>Invoice #: INV-0001<br>Date: August 5, 2025</div>
<div class="customer-details"><strong>Billed To:</strong><br>John Doe<br>789 Customer St.<br>[email protected]</div>
<table>
<thead><tr><th>Item</th><th>Description</th><th>Qty</th><th>Price</th><th>Total</th></tr></thead>
<tbody>
<tr><td>Product A</td><td>High quality item</td><td>2</td><td>$30.00</td><td>$60.00</td></tr>
<tr><td>Service B</td><td>Professional service</td><td>1</td><td>$80.00</td><td>$80.00</td></tr>
</tbody>
</table>
<div class="total"><strong>Subtotal:</strong> $140.00<br><strong>Tax (10%):</strong> $14.00<br><strong>Grand Total:</strong> <span style="color:#4CAF50;">$154.00</span></div>
</div>
<div class="footer">Thank you for your business - www.acmecorp.com</div>
</body>
</html>
"""
</code>📤 Generate and return PDF
The endpoint uses xhtml2pdf.pisa to render the HTML string into a PDF and returns it as a StreamingResponse with the appropriate Content‑Disposition header.
<code>@app.get("/generate-pdf")
def generate_pdf():
buffer = io.BytesIO()
pisa.CreatePDF(io.StringIO(html), dest=buffer)
buffer.seek(0)
return StreamingResponse(buffer, media_type="application/pdf", headers={"Content-Disposition": "attachment; filename=invoice.pdf"})
</code>🚀 Start the server
<code>uvicorn main:app --reload</code>Then open http://127.0.0.1:8000/generate-pdf in a browser; the PDF will start downloading.
🧠 Tips and extensions
Use Jinja2 templates to inject dynamic data such as items, totals, or customer information.
While xhtml2pdf works for basic PDF generation, consider WeasyPrint or ReportLab for more control.
Always sanitize user‑provided HTML to prevent injection attacks.
🏁 Conclusion
With just a few lines of code, you can generate professional PDF files directly from a FastAPI backend, making this approach ideal for invoices, certificates, receipts, or any document‑heavy workflow.
Code Mala Tang
Read source code together, write articles together, and enjoy spicy hot pot together.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.