Core concepts
The four inputs
Every BroadPaper render is a pure function of four things:
| Input | Who provides it | What it is |
|---|---|---|
| Template | Your users (via the designer) | JSON describing pages, sections, rows, columns and blocks, with bindings written as expressions. |
| Data sources | Your developers | Schemas describing the shape of data: client.firstName is a string, portfolio.holdings is a list of objects… |
| Data | Your application at runtime | A plain JSON object keyed by data source id. |
| Theme | Your developers / your customers | Colours, fonts, logo and brand details. Templates reference theme tokens, never literal brand colours. |
render(template, data, theme) is deterministic: the same inputs give the same pages, and the same PDF bytes as long as the clock, the fonts, the external assets and the renderer version are also the same. What each one guarantees spells that out.
The document model
Template
├── page size, orientation, margins, header/footer heights
├── styles named text styles (h1…small), table, card, spacing
├── header sections shown on every page (optionally different on page 1)
├── footer sections shown on every page
├── body sections
│ └── Section
│ ├── Row → Column → Blocks…
│ └── Blocks (heading, text, table, repeater, chart, custom…)
└── library reusable sections saved with the templateEvery node is { id, type, props, style?, children?, visibleWhen?, flow? }. Blocks are looked up in the block registry, which is how custom blocks become first-class citizens.
Design mode and preview mode
The designer has two modes that use the same rendering pipeline:
- Design shows tokens as chips (
Client › First name) so it is obvious what is dynamic, shows one representative repeater iteration, a few table rows, and keeps conditionally hidden content visible but dimmed. - Preview evaluates everything with a chosen sample data set. What you see is exactly the PDF.
Pagination
BroadPaper does not let the browser paginate. Each section is measured once as a continuous "galley"; a pure paginator then assigns clip windows to fixed-size pages honouring:
keepTogether(cards, KPIs, repeater items by default)keepWithNext(headings by default)- orphan/widow minimums for text lines and table rows
- repeated table headers on continuation pages
- explicit page breaks and
breakBefore/breakAfter - multi-column rows, cut at a y that is valid in every column
Anything taller than a page that cannot be split is cut at page boundaries with a warning rather than dropped. See docs/ARCHITECTURE.md in the repository for the full reasoning.
Style cascade
Theme defaults → Document text styles (h1, body, …) → Block defaults → Instance overridesChanging the document's H2 style restyles every H2. Instance overrides can be disabled by the host (features.styleOverrides = false) so templates stay on-brand.