Render an HTML Financial Report or Invoice to PNG in Python
Generate the report as HTML in your Python backend, hand it to Rendex, and get back a pixel-perfect PNG — charts, financial tables, certificates, and invoices included. One render_html() call replaces a hosted headless browser.
Last updated 2026-06-02
The Problem
You already produce reports, invoices, and financial tables as HTML on the server — it's the easiest format to template and style. But turning that HTML into a raster image to embed in a generated PDF or deliverable means running headless Chrome (Playwright or Puppeteer) inside your app: a heavyweight binary to install, version, and keep alive, plus the memory leaks and cold-start stalls that come with a long-running browser process. WeasyPrint and wkhtmltopdf drop modern CSS — Flexbox, Grid, web fonts — so your charts and tables don't look the way they do in a browser. None of this is the report logic you actually own.
The Solution
Render the HTML with the Rendex Python SDK instead. Pip-install rendex, call render_html(html, data=..., format="png") with your report markup, and get back PNG bytes from a single HTTP call — no browser to host, no binary to babysit. Rendex renders with a real, modern headless engine, so Flexbox, CSS Grid, web fonts, and print backgrounds all come through. Set device_scale_factor=2 for retina-crisp output and full_page=True so a long table isn't clipped. Write the bytes to disk, embed them in your generated PDF, or push them to storage — all inside the same script, all on the free tier.
How the Workflow Runs
Python script
Build the report HTML server-side (Jinja2, f-strings, or a charting lib's HTML/SVG export).
Rendex
render_html() posts the HTML to /v1/screenshot and returns PNG bytes at 2x (retina) resolution.
Save / embed
Write the PNG to disk, drop it into a generated PDF, or upload it to your report store — all from the same script.
Input → Rendered Output

Rendered by Rendex
What You Need
- A Rendex API key — the free tier gives you 100 renders/month with no card required.
- Python 3.9+ with the SDK installed: pip install rendex (single dependency, httpx).
- Your report, invoice, or table as an HTML string — templated however you like (Jinja2, f-strings, a charting library's HTML/SVG export).
- Somewhere to put the result: a file path, a PDF builder you embed the PNG into, or an upload target.
What This Recipe Uses
HTML to PNG
render_html() turns a raw HTML string into PNG bytes through one /v1/screenshot call — no Playwright or Puppeteer in your deployment.
Retina output
device_scale_factor=2 renders at 2x so financial tables, small type, and chart labels stay crisp when embedded in a PDF or slide.
Full-page capture
full_page=True renders the whole report, so a long itemized table or multi-section statement isn't clipped at the viewport height.
Mustache data{} templating
Pass one reusable HTML template plus a data object. {{company}}, {{period}}, and {{#rows}} loops fill in server-side, per report.
Build It
from rendex import Rendex
from pathlib import Path
rendex = Rendex("YOUR_API_KEY") # or Rendex(os.environ["RENDEX_API_KEY"])
# Your report HTML — templated however you already do it (Jinja2, f-strings,
# a charting library's HTML/SVG export). Here: a financial summary table.
html = """
<!doctype html>
<html>
<head>
<meta charset="utf-8" />
<style>
body { font-family: system-ui, "Segoe UI", Roboto, sans-serif;
color: #0f172a; margin: 0; padding: 40px; width: 900px; }
h1 { color: #ea580c; font-size: 24px; margin: 0 0 4px; }
.sub { color: #64748b; font-size: 13px; margin: 0 0 28px; }
table { width: 100%; border-collapse: collapse; }
thead th { text-align: left; font-size: 11px; text-transform: uppercase;
letter-spacing: .04em; color: #64748b;
border-bottom: 2px solid #e2e8f0; padding: 10px 12px; }
tbody td { border-bottom: 1px solid #f1f5f9; padding: 10px 12px; }
td.amt, th.amt { text-align: right; font-variant-numeric: tabular-nums; }
tfoot td { font-weight: 700; padding-top: 14px; }
</style>
</head>
<body>
<h1>{{company}} — Revenue Summary</h1>
<p class="sub">Period: {{period}}</p>
<table>
<thead><tr><th>Line item</th><th class="amt">Amount</th></tr></thead>
<tbody>
{{#rows}}<tr><td>{{label}}</td><td class="amt">{{amount}}</td></tr>{{/rows}}
</tbody>
<tfoot><tr><td>Total</td><td class="amt">{{total}}</td></tr></tfoot>
</table>
</body>
</html>
"""
result = rendex.render_html(
html,
data={
"company": "Acme Robotics, Inc.",
"period": "Q1 2026",
"rows": [
{"label": "Subscriptions", "amount": "€482,100"},
{"label": "Professional services", "amount": "€91,400"},
{"label": "Overages", "amount": "€12,750"},
],
"total": "€586,250",
},
format="png",
width=900,
full_page=True, # render the whole table, not just the viewport
device_scale_factor=2, # 2x retina — crisp when embedded in a PDF/deck
)
# result.image is the raw PNG bytes — save it, or embed it in a generated PDF.
Path("revenue-summary.png").write_bytes(result.image)
print("Rendered", result.metadata) # width/height/format/credits-remaining# Render the HTML report to a PNG, then drop it into a deliverable PDF.
# (reportlab shown; the PNG bytes work with any PDF library or a docx/pptx.)
from io import BytesIO
from rendex import Rendex
from reportlab.lib.pagesizes import A4
from reportlab.lib.utils import ImageReader
from reportlab.pdfgen import canvas
rendex = Rendex("YOUR_API_KEY")
result = rendex.render_html(
REPORT_HTML, # your templated HTML string
data=report_context, # the row's data for this report
format="png",
width=900,
full_page=True,
device_scale_factor=2,
)
# Build the PDF deliverable and place the rendered chart/table image.
pdf = canvas.Canvas("deliverable.pdf", pagesize=A4)
img = ImageReader(BytesIO(result.image))
pdf.drawImage(img, 48, 360, width=499, preserveAspectRatio=True, mask="auto")
pdf.drawString(48, 800, "Quarterly Report — generated automatically")
pdf.showPage()
pdf.save()# The same call without the SDK. --output writes the PNG bytes to a file.
curl -X POST https://api.rendex.dev/v1/screenshot \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
--output revenue-summary.png \
-d '{
"html": "<!doctype html><html><head><style>body{font-family:system-ui,sans-serif;color:#0f172a;padding:40px;width:900px}h1{color:#ea580c;margin:0 0 4px}table{width:100%;border-collapse:collapse}thead th{text-align:left;border-bottom:2px solid #e2e8f0;padding:10px 12px;font-size:11px;text-transform:uppercase;color:#64748b}tbody td{border-bottom:1px solid #f1f5f9;padding:10px 12px}td.amt,th.amt{text-align:right}tfoot td{font-weight:700;padding-top:14px}</style></head><body><h1>{{company}} — Revenue Summary</h1><p>Period: {{period}}</p><table><thead><tr><th>Line item</th><th class=\"amt\">Amount</th></tr></thead><tbody>{{#rows}}<tr><td>{{label}}</td><td class=\"amt\">{{amount}}</td></tr>{{/rows}}</tbody><tfoot><tr><td>Total</td><td class=\"amt\">{{total}}</td></tr></tfoot></table></body></html>",
"data": {
"company": "Acme Robotics, Inc.",
"period": "Q1 2026",
"rows": [
{ "label": "Subscriptions", "amount": "€482,100" },
{ "label": "Professional services", "amount": "€91,400" },
{ "label": "Overages", "amount": "€12,750" }
],
"total": "€586,250"
},
"format": "png",
"width": 900,
"fullPage": true,
"deviceScaleFactor": 2
}'100 free API calls/month — no credit card required. Get your API key and start building.
Reach for this pattern when your backend already speaks HTML and you need an image, not a webpage — a financial summary embedded in a board deck, an invoice rasterized into a PDF deliverable, a certificate generated per customer, or a chart baked into a nightly report. It is the right fit for the dev who renders dozens or thousands of these from a server script and does not want a headless browser in the deployment. Because render_html() takes raw HTML plus an optional Mustache data object, you keep templating where it belongs — Jinja2, a charting library's HTML export, or plain f-strings — and Rendex only handles the pixels. You get a real rendering engine, so the PNG matches what you'd see in Chrome: modern CSS, web fonts, and print backgrounds intact. Use device_scale_factor=2 for retina output and full_page=True so a tall report isn't cut off at the viewport. The SDK returns the bytes directly, so saving to disk, embedding into a PDF, or uploading to your report store is one more line. It runs on Rendex's free 100 renders a month with no card, which is enough to prove the pipeline before you scale the volume.