DocuForge logoDocuForge

blog

How to Generate Multi-Page PDF Statements

When your PDF pipeline cracks at 2 AM and customers are waiting on invoices, debugging headless Chromium is the last thing you want to do. This guide cuts to what actually matters for PDF generation for SaaS in production: templates that render the same way every time, render jobs that survive queue spikes, and an API surface with clear failure modes so on-call is boring.

Updated 2026-02-24

Problem

A backend worker needs to render documents idempotently from queue events and avoid duplicate PDFs during retries.

Treat rendering as a backend job pipeline: idempotent requests, queue control, and deterministic templates.

Typst playground

A backend worker needs to render documents idempotently from queue events and avoid duplicate PDFs during retries.

Queue receipt Typst template

Sample JSON payload

#set page(paper: "a4", margin: 18mm)
#set text(font: "Inter", size: 10pt)

#let data = sys.inputs

#text(size: 22pt, weight: "bold")[#data.title]
#v(4pt)
#text(fill: rgb("#64748b"))[#data.reference + " - " + data.date]

#v(12pt)
#block(
  fill: rgb("#f8fafc"),
  stroke: 0.5pt + rgb("#e2e8f0"),
  inset: 12pt,
  radius: 4pt,
)[
  #text(weight: "semibold")[#data.customer]
  #linebreak()
  #text(fill: rgb("#64748b"))[#data.status]
]

#v(12pt)
#table(
  columns: (1fr, auto, auto),
  inset: 7pt,
  stroke: 0.5pt + rgb("#e2e8f0"),
  [*Item*], [*Qty*], [*Amount*],
  ..data.line_items.map(item => (
    item.name,
    str(item.qty),
    "$" + str(item.amount),
  )).flatten(),
)

Queue render receipt

Idempotent

Queue render receipt

Idempotent

CustomerFulfillment Worker
ReferenceJOB-2026-8831
Date2026-06-16
Rows2
POST /v1/render

Solution & copyable API code

Feed render jobs through queues partitioned by document type or priority. This prevents a noisy workload from starving critical documents. Attach stable idempotency keys per business document event so retries do not create duplicate outputs.

const response = await fetch(`${process.env.DOCUFORGE_API_URL}/v1/render`, {
  method: "POST",
  headers: {
    "X-API-Key": process.env.DOCUFORGE_API_KEY,
    "Content-Type": "application/json",
  },
  body: JSON.stringify({ template_id: "tpl_how_to_generate_multi_page_pdf_statements", data: payload }),
});

const pdf = Buffer.from(await response.arrayBuffer());

Customization tips

  • Attach an idempotency key to each business event before rendering.
  • Keep retry metadata outside the PDF payload so document output stays deterministic.
  • Keep brand colors, spacing, and repeated footer copy in shared template variables.
  • Pass customer-specific values through JSON data instead of forking the template.
  • Store one approved sample payload per template so preview regressions are easy to catch.

Wichtigste Punkte

  • Queue partitioning and idempotency are mandatory at scale.
  • Template version IDs should be explicit in production render events.
  • Monitor usage and quotas by plan and document family.
  • Keep failure handling deterministic with typed error branches.

Why teams choose DocuForge for this workflow

  • Render API is queue-friendly with predictable request/response envelopes.
  • Usage and billing endpoints support operational visibility.
  • No headless Chrome dependency in the main render engine path.

Verwandte Inhalte

FAQ

How do we avoid duplicate documents on retries?

Use business-event idempotency keys and persist render outcomes before acknowledging queue completion.

What should we put in dead-letter queues?

Jobs with non-retryable schema/template failures and jobs that exceeded bounded retry policy.

Verwandte Inhalte

Schneller liefern mit einer Template-first API

Templates live testen und dieselbe Payload direkt in Produktion verwenden.