Build Your Own ReportGen: The Complete Guide to Running an HTML-to-PDF Service
November 2, 2026
The hosted reportgen.io API is shutting down on December 2, 2026.
If you use it today, your API key keeps working until then. This guide is our way of handing over what we learned: how to build and run your own HTML-to-PDF service, step by step, with code you can copy.
Every JavaScript and SQL sample in this guide was run and tested before publishing. It's plain Node.js, Puppeteer and Postgres. No magic, no vendor lock-in.
Who This Is For
You need to turn HTML (or a template plus JSON data) into PDFs: invoices, reports, certificates, receipts. You want to own the pipeline instead of renting it.
You don't need to copy our full architecture to do that. reportgen.io ran a Go API, Temporal workflows and two separate worker fleets (we wrote about it here). That made sense for a multi-tenant public API. For your own app, it's almost certainly too much. This guide starts tiny and only adds pieces when you actually need them.
The Guide
- A Minimal PDF Service in 40 Lines: one Express endpoint, one Chrome instance, HTML in, PDF out. Enough for a lot of apps.
- Templates, Page Numbers and Page Breaks: Handlebars templates with JSON data, headers and footers, tables that split cleanly across pages, web fonts.
- Async Jobs, Storage and Webhooks: a job queue in plain Postgres, retries that know when to give up, S3/R2 storage, signed webhooks.
- Running Chrome in Production: bounded concurrency, browser recycling, crash recovery and hard timeouts.
- Security: Templates Are Untrusted Code: how a PDF renderer leaks your cloud credentials, and how to stop it.
- Docker, Hosting and Scaling: a production Dockerfile, fonts, memory sizing and when to scale out.
Which Parts Do You Need?
| Your situation | Read |
|---|---|
| A few PDFs a day, users wait for the download | Parts 1, 2, 5 |
| Big reports, batch jobs, or users shouldn't wait | Add Part 3 |
| Steady traffic, or it must not fall over at night | Add Parts 4 and 6 |
| Users can submit their own templates or HTML | Part 5 is mandatory |
The Shape of What We'll Build
By the end you'll have two small processes:
- API: accepts a template and data, stores a job row in Postgres, returns
202 Acceptedwith a job ID. - Worker: claims jobs, renders HTML with Handlebars, prints it with headless Chrome, uploads the PDF to S3-compatible storage, and calls your webhook.
Both scale horizontally: run more workers and they share the queue safely. The only infrastructure is Postgres and a bucket, which most apps already have.
What We'd Do Differently
A few honest lessons from running a PDF API:
- Most PDF problems are CSS problems. Page breaks, fonts and margins cause far more support tickets than servers do. Part 2 is worth more than it looks.
- Chrome is the expensive part. Memory, not CPU, is what you run out of. Cap concurrency before anything else.
- Every job needs an ending. A job stuck in "pending" forever is worse than a failed one. Design for crashed workers from day one.
- Templates are code. If someone else can write them, they can make your server fetch things it shouldn't.
Let's start with Part 1: a minimal PDF service.