DocuForge logoDocuForge

blog

How to Generate Invoices Programmatically in 2026 (3 Approaches Compared)

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 how to generate invoices programmatically 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 browser-based PDF worker is adding seconds of cold-start latency and failing whenever print CSS behaves differently in production.

Remove browser rendering from the hot path, stabilize template inputs, and run rendering behind retry-aware queues.

Typst playground

A browser-based PDF worker is adding seconds of cold-start latency and failing whenever print CSS behaves differently in production.

Performance report 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(),
)

Render latency report

p95 under review

Render latency report

p95 under review

CustomerAcme Platform
ReferencePERF-2026-071
Date2026-06-16
Rows2
POST /v1/render

Solution & copyable API code

Profile p50/p95 render latency and separate CPU-bound rendering from request orchestration. Browser startup and CSS layout variability are frequent latency multipliers. Adopt idempotent job processing with bounded retries and observable failure codes so incidents can be triaged quickly.

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_invoices_programmatically_in_2026", data: payload }),
});

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

Customization tips

  • Measure p50 and p95 render time before and after each template change.
  • Avoid browser-only layout assumptions such as print CSS side effects.
  • 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

  • Latency problems usually come from runtime variability, not template complexity.
  • Queue retry policy should be explicit, bounded, and observable.
  • Use separate preview and production routes to contain blast radius.
  • Treat render failures as typed events, not generic logs.

Why teams choose DocuForge for this workflow

  • Predictable Typst rendering reduces variance compared with browser-based pipelines.
  • Usage limits and rate controls are enforced at API boundaries.
  • Structured errors make fallback and retry logic straightforward.

Verwandte Inhalte

FAQ

Can we keep Puppeteer and still improve performance?

Yes, with pooling and caching, but beyond a threshold operational cost and variance often remain high; measure against a deterministic alternative.

What metrics should we track first?

Track p50/p95 render duration, failure rate by error code, and queue retry depth per document type.

Verwandte Inhalte

Schneller liefern mit einer Template-first API

Templates live testen und dieselbe Payload direkt in Produktion verwenden.