DEV Community

William Silva
William Silva

Posted on

json-pdf-designer: an open-source PDF report designer you actually own the code of

Most "visual PDF designer" libraries for React give you a plugin system, a
declarative property schema, and a black box you configure from the outside.
The moment you need something they didn't anticipate, you're stuck.

I built json-pdf-designer the other way around: the <Designer>
component and its entire source live in your node_modules, ready to read
and edit. No plugin system. No declarative propPanel. Changing anything is
just editing the file.

What it does

  • Drag/resize canvas with a 5mm snapping grid
  • Text, table, image, repeated sections (master-detail), charts (pie/bar), and KPI cards
  • Bind every field to a real JSON data source — {token} and {FUNCTION(...)} templates (SUM, COUNT, AVG, DATE, CURRENCY, arithmetic...)
  • Real pagination, repeating header/footer, letterhead-style backgrounds
  • PDF generation is plain JS (pdf-lib) — the exact template designed in the browser generates on a Node backend too, no headless browser required
import { useState } from "react";
import { Designer, generatePdf, downloadPdf, type Template, type Binding } from "json-pdf-designer";
import "json-pdf-designer/style.css";

function Report() {
  const [template, setTemplate] = useState<Template>({
    page: { width: 210, height: 297 },
    schemas: [],
  });
  const [bindings, setBindings] = useState<Binding[]>([]);

  async function handleGenerate() {
    const data = await fetchMyData();
    const pdfBytes = await generatePdf(template, data, bindings);
    downloadPdf(pdfBytes, "report.pdf");
  }

  return (
    <>
      <Designer template={template} onChangeTemplate={setTemplate}
                bindings={bindings} onChangeBindings={setBindings} />
      <button onClick={handleGenerate}>Generate PDF</button>
    </>
  );
}
Enter fullscreen mode Exit fullscreen mode

Just shipped: multi-page reports

A Template can now hold several independent page designs
(Template.pages) — each with its own layout, generated into a single PDF
with {pageNumber}/{pageCount} continuing across them. The
report-builder example uses this for a page-tabs UI: one tab per page
design, shared JSON data source, one combined PDF out.

No React on your backend? No problem

generatePdf doesn't touch the DOM — only pdf-lib/fontkit. Import from
json-pdf-designer/server instead and skip React entirely:

// Node backend, no react/react-dom installed
import { generatePdf } from "json-pdf-designer/server";

const bytes = await generatePdf(template, data, bindings);
Enter fullscreen mode Exit fullscreen mode

Try it live

Three example apps run right in the browser, no install needed:

👉Try the live playground →

  • report-builder — the full designer with the ready-made UI kit
  • custom-ui — same <Designer>, your own CSS shell
  • headless-designer — no <Designer> at all, a hand-built canvas over just the data model

Full docs (EN/pt-BR): williamanjo.github.io/json-pdf-designer

npm install json-pdf-designer
Enter fullscreen mode Exit fullscreen mode

https://github.com/williamanjo/json-pdf-designer

npm: https://www.npmjs.com/package/json-pdf-designer

Would love feedback, issues, or a ⭐ if this is useful to you.

Top comments (0)