Python Guide

HTML to PDF in Python

Generate PDFs from HTML in Django, Flask, or FastAPI — without installing WeasyPrint, wkhtmltopdf, or any system-level dependencies. Works in Docker, Lambda, and any Python environment.

Why not WeasyPrint or wkhtmltopdf?

WeasyPrint has incomplete CSS support and breaks on modern layouts. wkhtmltopdf uses a decade-old WebKit build that doesn't support Flexbox or Grid properly. Both require system-level dependencies that complicate Docker builds and break on AWS Lambda entirely.

Papyr runs full Chromium on managed infrastructure. Your Python app makes one HTTP request — no system packages, no Dockerfile changes, no cold-start issues.

Basic usage

Use httpx or requests — no SDK needed.

Python
import httpx  # or use requests

def html_to_pdf(html: str) -> bytes:
    response = httpx.post(
        "https://api.getpapyr.dev/v1/render/pdf",
        headers={"Authorization": f"Bearer {os.environ['PAPYR_API_KEY']}"},
        json={
            "html": html,
            "options": {
                "format": "A4",
                "margin": {"top": "20mm", "right": "20mm",
                           "bottom": "20mm", "left": "20mm"},
            },
        },
    )
    response.raise_for_status()
    return response.content

# Usage
pdf_bytes = html_to_pdf("<h1>Hello, PDF!</h1>")
with open("output.pdf", "wb") as f:
    f.write(pdf_bytes)

Django view

Use Django's template engine to render your HTML, then send it to Papyr.

Django
# views.py
import httpx
from django.http import HttpResponse
from django.template.loader import render_to_string
import os

def invoice_pdf(request, invoice_id):
    invoice = Invoice.objects.get(pk=invoice_id)

    # Use Django's template engine to render HTML
    html = render_to_string("invoices/pdf.html", {"invoice": invoice})

    response = httpx.post(
        "https://api.getpapyr.dev/v1/render/pdf",
        headers={"Authorization": f"Bearer {os.environ['PAPYR_API_KEY']}"},
        json={"html": html},
    )
    response.raise_for_status()

    return HttpResponse(
        response.content,
        content_type="application/pdf",
        headers={"Content-Disposition": f'attachment; filename="invoice-{invoice.number}.pdf"'},
    )

FastAPI endpoint

Use httpx.AsyncClient with async/await for non-blocking PDF generation.

FastAPI
from fastapi import FastAPI
from fastapi.responses import Response
import httpx, os
from jinja2 import Environment, FileSystemLoader

app = FastAPI()
jinja = Environment(loader=FileSystemLoader("templates"))

@app.get("/invoices/{invoice_id}/pdf")
async def get_invoice_pdf(invoice_id: int):
    invoice = await db.get_invoice(invoice_id)

    template = jinja.get_template("invoice.html")
    html = template.render(invoice=invoice)

    async with httpx.AsyncClient() as client:
        r = await client.post(
            "https://api.getpapyr.dev/v1/render/pdf",
            headers={"Authorization": f"Bearer {os.environ['PAPYR_API_KEY']}"},
            json={"html": html},
        )
        r.raise_for_status()

    return Response(
        content=r.content,
        media_type="application/pdf",
        headers={"Content-Disposition": f'attachment; filename="invoice-{invoice_id}.pdf"'},
    )

Background tasks with Celery

For reports that take longer or need to be stored, generate asynchronously and upload to S3.

Celery task
# tasks.py — async PDF generation with Celery
from celery import shared_task
import httpx, boto3, os

@shared_task
def generate_and_store_report(report_id: int):
    report = Report.objects.get(pk=report_id)
    html = render_to_string("reports/pdf.html", {"report": report})

    response = httpx.post(
        "https://api.getpapyr.dev/v1/render/pdf",
        headers={"Authorization": f"Bearer {os.environ['PAPYR_API_KEY']}"},
        json={"html": html, "options": {"landscape": True}},
        timeout=30,
    )
    response.raise_for_status()

    s3 = boto3.client("s3")
    key = f"reports/{report_id}.pdf"
    s3.put_object(Bucket="my-docs", Key=key, Body=response.content,
                  ContentType="application/pdf")

    report.pdf_url = f"https://my-docs.s3.amazonaws.com/{key}"
    report.save()

Get your API key

Free tier: 100 PDFs/month. No credit card required.