DEV Community

Cover image for Generate PDFs from JSON in Any Language — One REST API (Python, Node, PHP, Go)
Gerardo Barrera
Gerardo Barrera

Posted on

Generate PDFs from JSON in Any Language — One REST API (Python, Node, PHP, Go)

Every backend eventually has to spit out a PDF — an invoice, a receipt, a report, a certificate. And in every language, the options are bad:

  • Headless Chrome / Puppeteer — you render HTML to PDF. Works until your data gets long: tables split across pages, fonts go missing, and you're babysitting a browser farm in production.
  • Native libraries (ReportLab in Python, PDFKit in Node, FPDF in PHP, gofpdf in Go) — you position everything by hand in code. Generic fonts, fragile layouts, and the design lives in your codebase forever.

There's a cleaner separation: design the document once, then feed it data over HTTP. Because it's just a REST call, the exact same approach works in any language.

The idea: template + data → PDF

  1. Design a reusable template once — visually, or by describing it to an AI — with placeholders like {{customer}} and {{total}}.
  2. POST your JSON to one endpoint with the template ID.
  3. Get back a finished, editable PDF.

No HTML/CSS, no headless browser, no fonts to ship. Here's the same request in five languages.

curl

curl https://api.pdfmakerapi.com/v1/render \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "template_id": "invoice_default",
        "data": { "number": "INV-1042", "customer": "Acme Inc.", "total": 49.00 } }' \
  --output invoice.pdf
Enter fullscreen mode Exit fullscreen mode

Node.js

const res = await fetch("https://api.pdfmakerapi.com/v1/render", {
  method: "POST",
  headers: {
    Authorization: `Bearer ${process.env.PDF_API_KEY}`,
    "Content-Type": "application/json",
  },
  body: JSON.stringify({
    template_id: "invoice_default",
    data: { number: "INV-1042", customer: "Acme Inc.", total: 49.0 },
  }),
});
require("fs").writeFileSync("invoice.pdf", Buffer.from(await res.arrayBuffer()));
Enter fullscreen mode Exit fullscreen mode

Python

import os, requests

res = requests.post(
    "https://api.pdfmakerapi.com/v1/render",
    headers={"Authorization": f"Bearer {os.environ['PDF_API_KEY']}"},
    json={
        "template_id": "invoice_default",
        "data": {"number": "INV-1042", "customer": "Acme Inc.", "total": 49.00},
    },
)
open("invoice.pdf", "wb").write(res.content)
Enter fullscreen mode Exit fullscreen mode

PHP

<?php
$res = file_get_contents("https://api.pdfmakerapi.com/v1/render", false, stream_context_create([
  "http" => [
    "method"  => "POST",
    "header"  => "Authorization: Bearer {$_ENV['PDF_API_KEY']}\r\nContent-Type: application/json",
    "content" => json_encode([
      "template_id" => "invoice_default",
      "data" => ["number" => "INV-1042", "customer" => "Acme Inc.", "total" => 49.00],
    ]),
  ],
]));
file_put_contents("invoice.pdf", $res);
Enter fullscreen mode Exit fullscreen mode

Go

body, _ := json.Marshal(map[string]any{
    "template_id": "invoice_default",
    "data":        map[string]any{"number": "INV-1042", "customer": "Acme Inc.", "total": 49.00},
})
req, _ := http.NewRequest("POST", "https://api.pdfmakerapi.com/v1/render", bytes.NewReader(body))
req.Header.Set("Authorization", "Bearer "+os.Getenv("PDF_API_KEY"))
req.Header.Set("Content-Type", "application/json")
res, _ := http.DefaultClient.Do(req)
defer res.Body.Close()
out, _ := os.Create("invoice.pdf")
io.Copy(out, res.Body)
Enter fullscreen mode Exit fullscreen mode

Same JSON, same endpoint, five languages. The PDF comes back in the response body.

Where the template comes from

You don't write HTML. You design the template in a visual, drag-and-drop editor — drop in text, tables, images, and {{variables}} — or describe it in plain English and let the AI build it. Change the design later without touching your code; your request body stays the same.

And because it renders from a structured template (not a screenshot of a webpage), layouts don't break when the data changes — and there's no headless browser, so it generates thousands of PDFs in seconds.

Generating in bulk

It's just HTTP, so loop your data:

for inv in invoices:
    pdf = requests.post(URL, headers=H, json={
        "template_id": "invoice_default", "data": inv,
    }).content
    open(f"{inv['number']}.pdf", "wb").write(pdf)
Enter fullscreen mode Exit fullscreen mode

Try it

PDFMakerAPI is free to start — 100 PDFs/month, no card. Design a template, grab an API key, and you're rendering PDFs from any language in a few minutes. You can also generate from no-code tools (Zapier, Make, n8n) or straight from AI agents (Claude, ChatGPT) — same templates, same output.

What's the worst PDF-generation setup you've had to maintain? Curious what people are running.

Top comments (0)