Skip to main content

Installation

npm install json-pdf-designer

Peer deps: react and react-dom (^18.0.0 || ^19.0.0) plus react-rnd (the canvas's drag/resize library) — all marked optional in peerDependenciesMeta, so a backend-only install never forces them on you (see Server-only usage). Installing <Designer>'s side of the package means installing all three:

npm install react react-dom react-rnd

pdfjs-dist is an optional peer dependency too — needed only for the PDF preview, which lives behind the json-pdf-designer/preview entry point. Install it if you use the preview:

npm install pdfjs-dist

Skipping it keeps ~35MB out of your node_modules; <Designer>, generatePdf, and downloadPdf never touch pdf.js. See Ready-made UI components for the preview and examples/no-preview for an app that never installs it.

Import the package's CSS once, in your app's entry point — it styles <Designer> itself (property panel, toolbar, tabs):

import "json-pdf-designer/theme.css";

theme.css is hand-written, standalone CSS — no build step, no Tailwind on either side, and it imports the package's reset itself, so this one line is all of it. Skipping it is a supported mode, not a mistake: the .jpd-* classes and data-* attributes are still in the DOM, and you style them with whatever you use. See Theming for the class and token contract, and json-pdf-designer/reset.css if you want the reset without our appearance.

Upgrading from 2.x?

json-pdf-designer/style.css is gone and has no alias — the old import fails with ERR_PACKAGE_PATH_NOT_EXPORTED. See Migrating to 3.0.0.

Basic usage​

import { useState } from "react";
import { Designer, generatePdf, downloadPdf, type Template, type Binding } from "json-pdf-designer";
import "json-pdf-designer/theme.css";

const initialTemplate: Template = {
page: { width: 210, height: 297 }, // A4 in mm
schemas: [],
};

function Report() {
const [template, setTemplate] = useState<Template>(initialTemplate);
const [bindings, setBindings] = useState<Binding[]>([]);

async function handleGenerate() {
const data = await fetchMyData(); // the real JSON that fills the fields
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>
</>
);
}

onChangeTemplate/onChangeBindings accept React's setState functional form ((prev) => next) — use the useState setter directly, as above, so you don't lose a field if two get added in quick succession.