Paginated PDF reports, designed inside your Angular or React application.
Formancy Report is a paid, source-available module that designs and renders paginated PDF reports. It builds on the Apache-2.0 formancy packages and runs in your application: you host it, and you hand it the data.
The quotation starter in the React builder, and beside it the Preview: the PDF the runtime and the renderer print from the same session, drawn by PDF.js on white paper, down to “Total CHF 3'143.00” (0010, 0011).
Taken by a script from the built Studio and held to the text it shows, as every picture under The Studio is.
- Design a report in a native React or Angular builder, with undo, and see what is wrong before anything renders (0012, 0009).
- Print it as a paginated PDF, in Node or in a browser, from your application's JSON. Totals are exact, values are formatted for the report's locale, and a report that cannot be drawn whole is refused, never truncated (0010, 0011).
- Read its data from Formancy Data 0.1.0, optionally and best-effort (0016).
- Print a formancy form's submissions, each against the exact form version that collected it: its labels in the report's language, a field the version moved refused rather than printed wrong, and a number by its digits or not at all. Your application hands over the version and the submission (0042).
- Start a report from a formancy form version: a first draft, the same for the same version, that names the version and reads its submissions, with notes of what it left out and assumed (0045).
- Review a formancy form's new version for a report: a version that moved, removed or retyped a field the report reads is refused until a resolution is applied, and anything else prints. Each resolution is a report-core command your application applies in one undo step -- a rename the form's author declared first, after which the versions that keep the field there print unreviewed -- and none is applied by itself (0048).
- Say where a render's data came from, in the manifest kept beside the PDF: the Data forms it read, or each submission of a formancy form with its form's id and the version that collected it, held to the syntax of identifiers (0041).
- QR codes, refused where they may not scan (0020).
- A Swiss QR-bill, its receipt and payment part at the foot of the last page, held to SIX's rules, and edited field by field in either builder: added from SIX's example, and warned of while it holds SIX's examples (0022, 0039).
- An EPC QR code for a SEPA credit transfer, held to the EPC's rules, with Austria's frame as an option (0025).
- An EN 16931 e-invoice, the German hybrid invoice: the Factur-X / ZUGFeRD XML is written from the values the page prints, and a final render is a PDF/A-3b that embeds it. It may name the parties' electronic addresses, the seller's contact and its business process, which a German business invoice does not need. Or it is an XRechnung 3.0 for a German public buyer, in CII: its XML, which your application takes from the resolved document and sends, is the invoice, and its PDF a copy labelled so on every page. France is not claimed. In a gate of its own, Mustang and KoSIT judge the XML of the repository's samples, an XRechnung's in KoSIT's XRechnung scenario, and veraPDF and Mustang the hybrids' PDFs (0027, 0031, 0037, 0038). Either kind is edited field by field in either builder, its terms in groups and its VAT categories a list, and an IBAN typed into the e-invoice, its QR-bill or its EPC QR code is the account all of them pay, in one step, where every one of them writes it in the report. Only its totals and its lines are held to the page: a seller, a date or a bank the page prints in its own words is yours to change with it. An e-invoice is edited and never made from nothing: a starter gives one (0044).
- Fonts: a report maps its
sans,serifandmonotokens to TrueType fonts your application approves, pinned by digest; the renderer reads each with report-spec's reader, embeds it from its bytes with the layout tables fontkit would run removed, and refuses a character the face lacks and a weight or slant it does not map. It prints Latin, Greek and Cyrillic, with their punctuation and symbols, and refuses every other script, right-to-left, shaping and CJK among them, in any face. A token left unmapped prints in Roboto (sans) or is refused; a QR-bill's slip keeps Liberation Sans (0040, 0043). - Images: a PNG, a JPEG, or an SVG logo drawn as vectors, made of shapes alone: no text, no embedded picture. Where your application gives the editor a store for them, a person adds one in either builder, and your application keeps its bytes (0030, 0033).
- The Studio, a playground with both builders and the PDF they print, beside them or in the design view's place, online at https://formancy.ai/report/ (Try it, 0018).
Planned and not here: the server and its jobs, reviewed AI changes, a published PDF viewer, and a consistent read from Formancy Data.
Status, 22 October 2026: 0.2.0, a developer preview, is released. Its eight packages are on npm, with provenance, and the GitHub release carries the signed SBOM and release report of the run that published them. What 0.2.0 holds and leaves out is in the changelog. Since then
mainholds 0.3.0's first work, under Unreleased there,@formancy/report-formsamong it, which no release has published yet. The report format is still version "0", the draft: a report written now is not promised to be read by a later release (0004).
Start with the getting-started guide: a quotation printed as a PDF in Node and in a browser, an XRechnung's XML taken from what the runtime resolved, the editor in a React page that saves and previews, and in an Angular page. The install gate compiles and runs its code against the packed packages (0015).
The Studio is online at https://formancy.ai/report/, and runs from a clone with Node 22.13 or later and corepack:
git clone https://github.com/sharkysan/formancy.ai-report.git
cd formancy.ai-report
corepack enable pnpm
pnpm install
pnpm demopnpm demo builds the packages and the Studio, then serves the built
Studio at http://localhost:4394. Pick a starter or a blank report, edit it
in either builder, and the Preview prints the PDF, Download PDF included.
The starters' data is synthetic and built in. The page asks no other site
for anything, and the browser gate fails if it does
(0023).
The online Studio is main's, deployed on every push to main, so it can
be ahead of the latest release. Nothing here checks what the address serves
(RELEASING.md). The product's
pages stand beside it, from https://formancy.ai/report/en/.
apps/studio is the playground, never published as a package
(0013).
The Preview stands beside the builder where the window has room for it,
and in the design view's place where it has not
(0029).
Add image… adds a PNG, a JPEG or an SVG from your computer, kept in the
page and sent nowhere
(0033).
The quotation's payment line edited in React to “Zahlbar innert 10 Tagen netto.”, then the switch moved to Angular. The Angular builder draws the same editor over the same session, so the edit is there, still selected, and so is its Undo; the Preview prints the edited line. Switching frameworks keeps the document, its history, the selection and the zoom (0012).
The equipment register with its Category column set to show the description instead. The builder says what that leaves behind before anything is rendered: “field "category" of the detail dataset is neither printed, drawn, ordered by, totalled nor read, and would be fetched for nothing”. A warning renders as written, so the Preview still prints the register grouped by site, with “Basel (12 items)” heading its group (0009).
The Book value column given the condition row.value > 1000, which reads a row where a column's condition has none. The builder marks it as an error, and the runtime refuses to print the preview rather than guess: “Preview: refused by the runtime, 1 problem.”, with its reason, “row has a value only in a table's cells and what an aggregate reads from each row”. Its button, “Select Book value”, pressed with Enter, selected the column and put the keyboard on its outline row, with the condition in its properties (0010).
More: At a phone's width
At 390 pixels wide, the service inspection starter as the Studio opens. The bar's controls take a line each, and the builder shows one pane at a time, chosen from its toolbar: it opens on the Preview, standing in the design view's place, with its first page at the width there is, “Page 1 of 2” (0029). Nothing scrolls sideways, which the Studio's browser gate measures down to 320 pixels.
Every picture in this section is taken by node scripts/pictures.mjs, in Chromium, from the built Studio driven through its controls, never by hand. pnpm --filter @formancy/report-studio test:pictures walks the same states and fails when a picture's elements no longer say what they said when it was taken, or when a caption quotes what they do not say, and pnpm test:repo holds every caption to its picture's record. CI runs both, in its browser job. Neither looks at the pixels: a change of colour or layout alone is seen only by looking. And a caption is held to the text its elements hold, not to how much of that text a pane has scrolled or cut out of the picture.
Each starter's PDF, and each invoice sample's, printed from its synthetic
data and drawn by PDF.js. report-render-pdf's pictures.test.ts fails
when a page stops printing or drawing what its picture shows, or its
caption quotes what the page does not print. It does not see a drawing
changed inside its own box, a rule, a border, an image or the colour of
text. The quotation and the invoices print an invented firm's mark, an SVG
your application hands over like any other asset
(0032).
The invoice starter is in German. The same invoice comes in French, Italian and English, with the slip's headings in SIX's words for each (0024).
The euro invoice prints beside its EPC QR code what the code holds, for the payer to compare with what the banking app read (0025). Samples from Austria, France, Italy, Spain, the Netherlands and Ireland sell parts for the boiler the starter services, at each country's standard VAT rate. No native speaker has reviewed their words, and they do not print all that each country's invoicing law asks of an invoice (0028).
The e-invoice starter is the euro invoice's firm invoicing a German business as an EN 16931 e-invoice, "Rechnung (ZUGFeRD)": the page prints each total the XML holds, computed once for both (0027), and either builder edits it field by field (0044). Its three other samples and the XRechnung's are the e-invoice gate's, not the Studio's.
The site visit is the report @formancy/report-forms generates from a
formancy form version, a first draft: its labels from that version in the
report's language, a group a section, the repeater asked for its table,
and a submission of that version read into it. Its notes, which no page
shows, list what it left out and assumed
(0045).
Your application resolves a report against the data it may show, then
renders it. Nothing here fetches data or follows an address: the report
names its datasets and assets, and your application hands over both. Here
report is the quotation starter, data its synthetic data and logo its
mark's bytes and digest, handed over when the runtime asks for it:
import { resolveReport } from '@formancy/report-runtime'
import type { ImageResolver } from '@formancy/report-runtime'
import { renderPdf, RenderRefusal } from '@formancy/report-render-pdf'
const images: ImageResolver = (ref) => (ref.asset === 'logo' && ref.digest === logo.digest ? { mediaType: 'image/svg+xml', bytes: logo.bytes } : undefined)
const resolution = resolveReport(report, data, { mode: 'final', parameters: {}, images, creationDate: '2026-10-15T08:00:00Z' })
if (!resolution.ok) throw new Error(resolution.errors.map((error) => `${error.code} at ${error.path}`).join('; '))
const pdf: Uint8Array = await renderPdf(resolution.document, { maxPages: 50 })resolveReport refuses, with every reason at once, a report the editor's
diagnostics refuse, data that breaks its contract, an asset your
application has no bytes for (missing-asset) and, in a final render, incomplete data.
renderPdf rejects with a RenderRefusal whose code says what stopped
it: too-many-pages, content-too-large, invalid-asset, cancelled or
unsupported. Neither returns part of a document. The install gate runs
these lines from the packed packages (scripts/install-fixture/consume.ts).
| Package | What it is |
|---|---|
@formancy/report-spec |
The report format, version 0 (a draft): its types, validation with stable error codes, a canonical hash, and the rules of a QR-bill, an EPC QR code, an e-invoice, an SVG, an image's bytes and a font's (0007). |
@formancy/report-core |
Commands over a report, as functions and as data, several as one batch, a session that commits only what validates, with exact undo and redo, and one compilation of every expression for the diagnostics and the runtime (0008, 0009, 0048). |
@formancy/report-runtime |
Resolves a report against its data into a renderer-neutral document, exact numbers as strings and totals rounded once, and writes an e-invoice's XML, or refuses with every reason; its manifest says what a render was resolved from, the data's source among it (0010, 0041). |
@formancy/report-render-pdf |
Draws that document with pdfmake, in Node and through a browser entry, and refuses what it cannot draw whole rather than return a partial PDF (0005, 0011). |
@formancy/report-data-provider |
Reads a report's data from a Formancy Data 0.1.0 server, labelled best-effort (0016). |
@formancy/report-forms |
Reads a report's data from a formancy form's submissions, against the exact version that collected each, as your application hands them over, host-captured (0042); and starts a report from a form version, a first draft whose notes list what it left out (0045); and reviews a form's new version for a report, a version that moved, removed or retyped a field it reads blocking until a resolution -- a declared rename first, report-core's commands as data -- is applied (0048). |
@formancy/report-builder-core |
The editor model both builders render, with no framework, in English, Swiss German and Swiss French; BUILDER_SCENARIOS is what every builder must do (0012). |
@formancy/report-builder-react |
The builder for React 19. |
@formancy/report-builder-angular |
The builder for Angular 22: standalone components, signals and OnPush, no zone.js. |
Beside them, apps/studio is the Studio, apps/pages the
product's pages, static HTML in four languages
(0021),
and @formancy/report-corpus the private test support that proves a
rendered PDF
(0003);
none is published.
Source-available, not open source. The formancy packages this module builds on stay Apache-2.0; this module does not. It is on Formancy Data's terms.
| Use | Terms |
|---|---|
| Read the source and fork the repository | Free |
| Evaluate, develop and test — including in companies | Free |
| Run in production, internally or customer-facing | Paid licence |
| Redistribute inside another software product | Separate commercial agreement |
| Customer data, report templates and generated documents | Remain the customer's |
LICENSE.md has the terms. It is Version 1, in force from 14 October 2026, and says at the top that no lawyer has reviewed it. A production licence is requested from info@formancy.ai, as its section 9 says.
Why a paid module exists beside an Apache-2.0 project is decision
0001.
Running @formancy/report-data-provider in production needs a Formancy
Data production licence as well, for the @formancy/data-client it
depends on
(0016).
Report depends on the released @formancy/* packages at exact versions and
copies none of their code
(0002). A
report is its own document, with pages, flowing tables, groups and totals,
not a widened formancy form
(0007).
Formancy Data is optional: without it, your application supplies the data.
With it, one package reads a published Data form's records, as the person
whose token your application supplies, so Data's policy decides every answer
(0016).
Data never depends on Report. A formancy form's submissions are read
through @formancy/spec's own validator, hash and labels, along scopes a
test holds to formancy's own data paths, by a package that depends on
formancy alone, never on Data
(0042).
Node 22.13 or later, which pdfjs-dist 6.4 needs, and pnpm through corepack.
corepack enable pnpm
pnpm install
pnpm build
pnpm testWhile working on the Studio, pnpm --filter @formancy/report-studio dev
serves it on port 4394, once pnpm build has built the packages it
imports.
CI runs the gates of .github/workflows/gates.yml on every pull request,
and a release runs them before it publishes. Every run ends in a release
report of what it was tested on and what it found, and a release publishes
only what its own run's report passed
(0014).
CLAUDE.md gives each gate's command and the bar a change
meets; Verification says what each
gate proves and what it does not.
| Tested with | Declared as | |
|---|---|---|
@formancy/data-client |
0.1.0 | 0.1.0 |
@formancy/data-core (transitive) |
0.1.0 | |
@formancy/expressions |
0.7.0 | 0.7.0 |
@formancy/spec (transitive) |
0.5.0 | |
@formancy/spec |
0.7.0 | 0.7.0 |
@formancy/themes |
0.7.0 | 0.7.0 |
tslib, by @formancy/report-builder-angular |
2.8.1 | ^2.8.0 |
pdfkit, by @formancy/report-render-pdf |
0.19.1 | 0.19.1 |
pdfmake, by @formancy/report-render-pdf |
0.3.11 | 0.3.11 |
uqr, by @formancy/report-render-pdf |
0.1.3 | 0.1.3 |
@noble/hashes, by @formancy/report-spec |
2.4.0 | 2.4.0 |
@angular/core, a peer of @formancy/report-builder-angular |
22.2.2, in its suites | ^22.0.0 |
react, a peer of @formancy/report-builder-react |
19.3.1, in its suites | ^19.0.0 |
react-dom, a peer of @formancy/report-builder-react |
19.3.1, in its suites | ^19.0.0 |
| Node, the workspace | >=22.13.0 |
|
| Node, CI | 22 | |
| pnpm | 12.4.2, through corepack | pnpm@12.4.2, the root's packageManager |
| Chromium | 156.0.8078.4, the chromium-headless-shell Playwright 1.64.1 pins (revision 1248) | |
typescript |
6.0.3 | ~6.0.3 |
vitest |
5.0.4 | ^5.0.1 |
pdfjs-dist |
6.4.299 | 6.4.299 |
jsdom |
30.1.2 | |
axe-core |
4.14.0 | |
playwright |
1.64.1 | ^1.63.0 |
What a run actually ran -- the Node of each job, the React and Angular each install host resolved at both ends of the builders’ peer ranges, and the Chromium the browser gate launched -- is in the release report every CI run produces and every release attaches, signed.
docs/getting-started.md— from an empty npm project to a PDF and the editor in your page, its code compiled and run by the install gate.docs/README.md— the architecture, in arc42's sections.docs/decisions/— every decision somebody could helpfully undo, with what verifies it.CHANGELOG.md— what changed and why.CONTRIBUTING.mdandCLA.md— contributions are taken under a CLA, checked on every pull request.SECURITY.md— how to report a vulnerability.RELEASING.md— how a release is cut, with provenance, a signed SBOM and the signed release report.docs/release/— what the release report holds and what fails it, with the release gates and the known limitations.



















![The site visit starter, generated from version 2 of a formancy form: a line for each answer, from Objekt: Bahnhofplatz 1, 3011 Bern, the inspector, the ticked boxes and the matrix under their headings, Elektrik: [x] among them, the findings one table under Befunde, and the page counted, 1 / 1](/sharkysan/formancy.ai-report/raw/main/docs/images/starter-site-visit.png)