Quick start

Step by step, from nothing to a working builder that saves and loads. No prior knowledge of the package assumed.

@storvexa/pdf-builder Chapter 3 of 14
Documentation

Words we use

Four words appear constantly in these pages. If they already mean something to you, skip ahead — but they are worth two minutes, because everything else builds on them.

WordWhat it means here
Document A plain JavaScript object describing one printable file — its pages, its blocks, its colours. It is data, not a PDF. You can print it with console.log, store it in a database column, and email it to yourself. Everything the builder does is "change this object".
Block One piece of content on the page — a title, an address, a table of line items, a signature. Your users drag them from a list on the left onto the page in the middle.
Prop A setting you pass to the component, written like an HTML attribute: <PdfBuilder readOnly />. Every feature in this manual is switched on by passing a prop.
Callback A function you hand to the package so it can call you when something happens. onSave is a callback: the package does not know how to save, so it calls your function and lets you do it.
Why callbacks, and not settings? Because the package has no idea where your data lives, what your login system is, or which cloud you use. Rather than guess, it asks you. That is the whole philosophy in one sentence.

Before you start

You need two things.

  1. Node.js installed, so you can run npm. Check by opening a terminal and typing node -v. If you see a version number, you are fine.
  2. A project with a JavaScript build step. A React app, a Symfony app with Webpack Encore, a Laravel app with Vite — any of these. If you have none of these yet, the fastest starting point is npm create vite@latest my-app -- --template react.
You do not need to know React well. You need to be able to put a component on a page. If you can do that, everything below is copy, paste and adjust.

Step 1 · Install it

In your project folder, run:

npm install @storvexa/pdf-builder react react-dom

Why three packages and not one?

react and react-dom are the engine the builder runs on. The package deliberately does not bring its own copy, because if two copies of React end up on one page, things break in confusing ways. So it uses yours.

Already have React? Installing it again changes nothing. npm sees it is there and moves on. It is safe to include in the command either way.

You should now see

added 3 packages in 4s

Step 2 · Put it on the screen

Create a file. This is the entire thing — copy it exactly.

// src/Editor.jsx import { PdfBuilder } from '@storvexa/pdf-builder' import '@storvexa/pdf-builder/styles.css' export function Editor() { return <PdfBuilder /> }

Three lines matter, and it is worth knowing what each does:

import { PdfBuilder } from … Brings the component into your file.
import '…/styles.css' Brings in the appearance. Forget this and you get a page of plain unstyled text that looks broken. This is the most common first mistake.
<PdfBuilder /> The whole editor. No settings required.

Now render <Editor /> wherever your app shows pages.

Step 3 · Give it a height

At this point most people see almost nothing — a thin sliver, or an empty strip. That is expected, and it is not broken.

The builder fills whatever box you put it in. If that box has no height, the builder has no height. It is the same reason an empty <div> takes up no space.

So give it a box with a real height:

export function Editor() { return ( <div style={{ height: '80vh' }}> <PdfBuilder /> </div> ) }

80vh means "80% of the height of the browser window". Anything real works — 700px, calc(100vh - 64px) if you have a fixed navbar above it.

Checkpoint · what you should see

The three parts of the screen
Blocks
Title
Company
Line items
Canvas
INVOICE
Drag a block here
Properties
Select a block to edit it
What each part is for

Left — Blocks. The catalogue. Drag one onto the page.

Middle — Canvas. The page itself, at real proportions. This is what prints.

Right — Properties. Click a block and its settings appear here.

Try it now: drag "Title" onto the page, then click it and change the words.

Everything already works. 44 blocks, 17 ready-made document designs, undo, redo, preview and PDF export — with zero settings. What follows is about connecting it to your application.

Step 4 · Catch what the user made

The builder is working, but if the user refreshes the page their work is gone. Nothing has been stored anywhere yet.

To get hold of the work, add onChange. The builder calls it every time anything changes, and hands you the document.

<PdfBuilder onChange={(doc) => console.log(doc)} />

Open your browser console, type something into a block, and watch. You will see a plain object appear:

{ version: 3, page: { size: 'A4', orientation: 'portrait', … }, tokens: { primaryColor: '#2563eb', locale: 'en-US', currency: 'USD', … }, rows: [ … ], ← your blocks live in here header: null, footer: null, }
That object is the whole document. There is nothing else. Save it and you have saved the user's work. There is no hidden state, no server-side session, no temporary file. If you can store a JSON string, you can store a document.
onChange fires on every keystroke. That is useful for a "you have unsaved changes" flag. It is not where you send a request to your server — you would send one per letter typed. Use Step 5 for that.

Step 5 · Save it

Add onSave and a Save button appears in the toolbar. When the user clicks it, the builder calls your function with the document. What happens next is entirely up to you.

<PdfBuilder onSave={async (doc) => { await fetch('/api/documents/42', { method: 'PUT', headers: { 'content-type': 'application/json' }, body: JSON.stringify(doc), // ← turn the object into text to send it }) }} />

On your server, store that JSON text in a column. That is the whole storage design.

Write async and await, as above. That is what lets the builder know the save is still in progress, so the button can show a spinner and stop the user clicking twice. Leave them out and the button reports success instantly — including when the request later fails.

Step 6 · Load it back

Hand the stored object back through defaultDocument:

const saved = JSON.parse(rowFromDatabase.document_json) <PdfBuilder defaultDocument={saved} onSave={save} />

That is it. You now have a full save-and-load cycle.

Store the object exactly as you received it. Do not remove fields you do not recognise, and do not delete version. That number is how a document saved today still opens after a future upgrade — the package reads it and quietly brings the document up to date.
Where does the PDF come from? You do not store one. The user clicks Export and the PDF is produced from the document, in their browser, at that moment. You store the small JSON object, not a large binary file.

Step 7 · Language and currency

By default a new document is US English with dollars. To start somewhere else, use createDocument:

import { PdfBuilder, createDocument } from '@storvexa/pdf-builder' <PdfBuilder defaultDocument={createDocument({ locale: 'en-IN', currency: 'INR' })} />
localeHow things are written — date order, decimal separators, digit grouping. 'en-IN' gives 1,23,456.00; 'de-DE' gives 123.456,00.
currencyWhich money it is. A three-letter code — 'INR', 'USD', 'EUR'. Never a symbol.
These are two separate settings on purpose. An Indian company invoicing an American client writes Indian English and charges dollars. If one were derived from the other, that document could not exist.
Use a hyphen, not an underscore. en-IN is correct. en_IN is what PHP, Laravel and Rails hand you, and the browser's formatting tools reject it outright. We convert it for you here — but nothing else in your app will.

If you get stuck

Almost every first-time problem is one of these five.

What you seeWhat it means
Plain text, no colours or panels The stylesheet was not imported. Check line 2 of Step 2. If you use Symfony Encore, also check that your layout links the JavaScript entry's CSS file, not only css/app.
Nothing at all, or a thin strip No height. Step 3.
Invalid hook call in the console Two copies of React on the page. Run npm ls react — if it lists React twice, that is the cause.
An empty box, and no error anywhere The component never mounted. In Symfony UX React this means the components were never registered; elsewhere it usually means the target element did not exist yet when your script ran.
It works, then breaks after a refresh You are not loading the saved document back. Step 6.

Longer list, including framework-specific traps, in Installation and API reference.

When you are ready for more

You have a working, saving editor. Everything below is optional, for when you need it.

Controlling it from your own buttons

Suppose Save lives in your toolbar, not the builder's. You need a way to say "give me the document now". That is what a ref is — a handle onto the component.

import { useRef } from 'react' function Editor({ saved }) { const builder = useRef(null) // starts empty; React fills it in return ( <> <button onClick={() => save(builder.current.getDocument())}>Save</button> <button onClick={() => builder.current.undo()}>Undo</button> <PdfBuilder ref={builder} onReady={(handle) => { if (saved) handle.loadDocument(saved) }} /> </> ) }
getDocument()The document right now.
loadDocument(doc)Replace it. Counts as one undo step.
undo() / redo()The same as the toolbar buttons.
builder.current is null until the component has appeared on screen. That is why loading happens inside onReady — it fires at the exact moment the handle becomes usable.

Two ways to hold the document

PropWho owns the documentUse it when
defaultDocument The builder Almost always. Simplest and fastest, and undo works with no effort from you.
document You Only when something outside the builder must change the document while the user is editing.
The second one redraws the whole page on every keystroke. It is the same trade-off as a controlled input in any React form — but the canvas is a great deal bigger than a text box. Start with defaultDocument.

Where to go next

Core conceptsHow the document is put together, and where a style comes from. Read this before customising anything.
Images & dataUploading logos, and putting real customer details into a reusable template.
CustomisingHide fields, hide interface, make it look like your product.
Custom blocksBuild a block of your own.