This content originally appeared on DEV Community and was authored by Gerardo Barrera
If you've ever had to generate PDFs from a Node app — invoices, receipts, reports, certificates — you've probably reached for
Puppeteer or some headless-Chrome setup. It works… until you have to run it in production. Now you're shipping a 300MB Chromium
binary, babysitting a browser process, fighting memory leaks, and your "render a PDF" endpoint times out under load.
There's a simpler way: describe the document as JSON, POST it to an API, get a PDF back. No browser. In this post I'll show how
to go from a JSON payload to a finished, editable PDF in about 15 lines of Node.
Full disclosure: I built PDFMakerAPI, the tool I'm using here. There's a free tier, no API key needed to
try it, and the code below runs as-is.
## The idea: separate the data from the design
The trick to sane PDF generation is to stop gluing strings of HTML together. Instead you keep two things separate:
- The design — a layout of text, tables, and containers (you can design this visually, or describe it as JSON).
- The data — the actual values that change per document.
You merge them with one request and get back a link to the finished PDF. Let's do it.
## Step 1 — Describe the document as JSON
A document is just a tree of nodes. Here's a minimal one — a short welcome letter with a {{customer_name}} placeholder:
const document = {
name: "Welcome Letter",
pageSize: "letter",
variables: [
{
id: "var_name",
name: "customer_name",
type: "text",
label: "Customer Name",
defaultValue: "Alex Morgan",
},
],
children: [
{
id: "title",
name: "Title",
type: "text",
order: 1,
width: "full",
content: "Welcome aboard 🎉",
fontSize: "xl",
fontWeight: "bold",
style: { textColor: "#111827" },
},
{
id: "body",
name: "Body",
type: "text",
order: 2,
width: "full",
content:
"Hi {{customer_name}}, thanks for joining. This whole PDF was generated from JSON in a single API call — no headless browser
involved.",
fontSize: "md",
style: { textColor: "#374151" },
},
],
};
Anything in {{double_braces}} is a variable, so you can reuse the same layout with different data. The full schema (containers,
tables, images, fonts, page settings) is in the docs and repo — but the shape
above is all you need to start.
## Step 2 — POST it and get a link back
const res = await fetch("https://api.pdfmakerapi.com/api/v1/documents", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ document }),
});
const { id, url } = await res.json();
console.log(url);
// → https://app.pdfmakerapi.com/d/<id>
That's it. The response is { id, url }, where url opens the finished document.
Prefer curl?
curl -X POST https://api.pdfmakerapi.com/api/v1/documents \
-H "Content-Type: application/json" \
-d '{ "document": { "name": "Welcome Letter", "pageSize": "letter", "children": [ { "id": "t", "type": "text", "order": 1, "width":
"full", "content": "Generated from JSON ✅", "fontSize": "xl", "fontWeight": "bold" } ] } }'
## Step 3 — The part Puppeteer can't do: an editable result
Here's the difference that matters. The link you get back isn't a flat, one-shot render — it opens an editable document. You (or
anyone you send the link to) can change any field in the browser and the preview updates live, then download the PDF.
Here's a real one generated this way — open it and edit a field:
👉 Live invoice example
That turns out to be a big deal in practice: your app drafts the document, but a human can review and fix it before it goes out —
instead of trusting whatever got rendered.
## Bonus: you don't even have to write the JSON
If hand-building the node tree feels tedious, you can skip it entirely. PDFMakerAPI ships an MCP server, so an AI agent (Claude,
Cursor, ChatGPT, etc.) can generate the document structure from a plain-English prompt:
{
"mcpServers": {
"pdfmakerapi": { "command": "npx", "args": ["-y", "@pdfmakerapi/mcp"] }
}
}
Then just ask: "make an invoice for Acme with 3 line items" and it returns the same kind of editable link. Same engine, no JSON by
hand.
## When you should NOT use this
Being honest: an API isn't always the right call.
- If you need to screenshot an existing web page to PDF, a headless browser is the right tool — that's literally what it's for.
- If you're generating one PDF, once, locally, a library like
pdfkitis fine.
Where this approach wins is generating structured documents from data, repeatedly, in production — invoices, receipts, reports,
certificates — without running a browser farm.
## Wrap-up
To generate a PDF from JSON in Node:
- Describe the document (or let an AI agent describe it).
-
POSTit to/api/v1/documents. - Get back a link to an editable, downloadable PDF.
No Chromium, no rendering servers, no timeouts. If you want to try it, the free tier gives you 100 PDFs/month with no card, and the MCP server is open source (MIT) on GitHub.
If you're generating PDFs in production today — what are you using, and what's been the most painful part? Curious to hear in the
comments.
This content originally appeared on DEV Community and was authored by Gerardo Barrera
Gerardo Barrera | Sciencx (2026-06-20T04:29:56+00:00) How to Generate a PDF from JSON in Node.js (without a headless browser). Retrieved from https://www.scien.cx/2026/06/20/how-to-generate-a-pdf-from-json-in-node-js-without-a-headless-browser/
Please log in to upload a file.
There are no updates yet.
Click the Upload button above to add an update.