Skip to content

PDF Options & Templating

{
"html": "<h1>Invoice</h1>",
"format": "A4",
"landscape": false,
"print_background": true,
"margins": { "top": 0.4, "right": 0.4, "bottom": 0.4, "left": 0.4 }
}

margins are in inches. print_background: true (the default) renders CSS backgrounds and colors — turn it off for a print-style black-and-white document.

{
"html": "<h1>{{ title }}</h1><p>Total: {{ total }}</p>",
"variables": { "title": "Invoice #142", "total": "€1,200.00" }
}
<table>
{% for item in items %}
<tr><td>{{ item.description }}</td><td>{{ item.amount }}</td></tr>
{% endfor %}
</table>
{ "items": [{ "description": "Design work", "amount": "€500.00" }] }

Any Jinja2 built-in filter works, e.g. {{ qr_data | urlencode }} for safely embedding a variable inside a URL (handy alongside a QR-code image service in an <img src>).

The renderer is a real, internet-connected headless browser — <link>-based Google Fonts and <img> tags pointing at public URLs work exactly as they would in any browser.

By default the renderer prints once the page fires load. That’s too early for a page whose real content arrives afterwards — a chart drawn by JavaScript, a table fetched on mount, a slow webfont. Use page_load to say what “ready” means:

{
"html": "<div id=\"report\"></div><script>/* fills #report */</script>",
"page_load": {
"wait_until": "networkidle0",
"timeout_ms": 15000,
"wait_for_selector": "#report"
}
}

All three fields are optional:

  • wait_until — load (default), domcontentloaded, networkidle0 (no network connections for 500ms) or networkidle2 (at most 2). networkidle0 is the usual choice for a page that fetches its own data.
  • wait_for_selector — a CSS selector that must appear before printing, for content that shows up after wait_until has already fired. This is the most reliable signal when you control the markup: render a known element last, and wait for it.
  • timeout_ms — how long to wait, 1000–120000, default 30000. It’s also clamped to the service’s own render budget, so asking for more than the server allows quietly gets the server’s limit instead of failing.

If the wait runs out — a selector that never appears, a page that never goes idle — the request fails with 504 PDF_RENDERING_TIMEOUT. A selector typo looks exactly like a slow page, so check it first.

Waiting longer than you need costs you latency on every render: prefer a precise wait_for_selector over a large timeout_ms.

  • HTML input and the rendered PDF both have size ceilings, and there’s a page-count ceiling — a document that’s too big or has too many pages fails clearly (422/413) rather than hanging.
  • Rendering has a timeout; a page that never finishes loading fails as 504 PDF_RENDERING_TIMEOUT instead of hanging the request.
  • Only HTML+CSS is accepted as input — there’s no “render this URL” endpoint (a deliberate scope limit, not a missing feature).