@jasy/pdf
jasy
ZUGFeRD & XRechnung e-invoices in pure TypeScript - generate, validate, read.
A Flutter-style PDF engine underneath. The EN-16931 maths done for you. Zero Java, zero upload.
npm i @jasy/zugferd · npx @jasy/cli · MIT
Don't believe us. Point it at your own invoice.
npx @jasy/cli validate ./your-invoice.pdf
That ✓ is the same official KoSIT Schematron + veraPDF the big players run - against the EN 16931
and the German XRechnung (BR-DE) rules, plus PDF/A-3. Your file, your terminal, in seconds. No
account, no upload, nothing leaves your machine.
Java has Mustang. PHP has horstoeko. Python has factur-x.
Node had nothing. Now it has jasy.
You bring the line items. jasy does the rest.
You never compute a total, a VAT breakdown or a rounding again. You hand jasy the line items - it derives the document totals and the EN-16931 VAT breakdown, spec-correct. That is why the invoices validate: the amounts are correct by construction, so the single biggest class of EN-16931 failures (the BR-CO total checks) simply cannot happen.
import { renderZugferd } from "@jasy/zugferd";
const { bytes, xml } = await renderZugferd({
number: "RE-2026-001",
issueDate: "2026-06-17",
currency: "EUR",
seller: {
name: "Northwind Studio GmbH",
vatId: "DE123456789",
address: { city: "Berlin", postCode: "10115", country: "DE" },
},
buyer: {
name: "Globex Corporation Ltd",
address: { city: "Munich", postCode: "80331", country: "DE" },
},
lines: [
{
name: "Brand identity design",
quantity: 2,
unit: "HUR",
netUnitPrice: 100,
vat: { category: "S", ratePercent: 19 },
},
],
});
// bytes -> a valid ZUGFeRD PDF/A-3 · xml -> the embedded EN-16931 XML
// totals, tax breakdown and rounding: computed for you.
One object in. A conformant, human-readable PDF/A-3 and the EN-16931 CII/UBL XML out. ZUGFeRD and XRechnung work out of the box - no config, no template wrangling.
The whole loop, from the terminal
jasy read invoice.pdf # identify + show: parties, line items, totals
jasy validate invoice.pdf # EN 16931 + XRechnung + PDF/A (exit 1 if invalid)
jasy export invoice.pdf -o out.xlsx # read it back: JSON · TXT · Excel
Reads CII and UBL, real third-party invoices included - not just our own output.
A PDF engine that reads like Flutter
Underneath it all is a declarative, component-based PDF engine - written from the byte stream up, no
headless browser, no pdf-lib. You describe the document; it lays it out and paginates.
import { Document, Page, Column, Text, renderToBytes } from "@jasy/pdf";
const pdf = await renderToBytes(
Document([
Page({ size: "A4", margin: 48 }, [
Column({ gap: 8 }, [
Text("Invoice RE-2026-001", { size: 24, bold: true, color: "steelblue" }),
Text("Thank you for your business.", { color: "gray" }),
]),
]),
]),
);
Real text layout (Adobe AFM metrics, kerning, word-wrap), flexbox-style gap / justify / align,
boxes with radius and alpha, images, custom TrueType fonts, AES-256 password encryption, and real
pagination - content that overflows flows onto the next page, headers and footers repeat.
Under the hood - and every bit is real
This is not a wrapper around someone else's renderer. It is built from the byte stream up, and you can verify all of it:
- Hand-rolled PDF writer. No
pdf-lib, no PDFKit, no Java, no headless Chrome - the byte stream is ours. - Font subsetting. Only the glyphs you actually use are embedded, with the PDF/A subset tag - a 740 KB font ships as a ~76 KB subset, and the text stays copy- and searchable.
- Compression. Content streams and images are FlateDecode-compressed; the spreadsheets
jasy exportwrites are real.xlsxZIPs we deflate with our own writer and CRC32, zero dependencies. - AES-256 encryption, pure JS. Lock a PDF with
renderToBytes(doc, { encrypt: { userPassword } })- the newest standard handler (AES-256, R6 / ISO 32000-2) via the platform WebCrypto: no native crypto, no deps, the same code in Node and the browser. Owner password + permissions optional. - Real font metrics. Text is laid out with the Adobe AFM metrics of the standard-14 fonts - kerning and word-wrap are computed, not guessed.
- PDF/A-3, matched not approximated. The conformance graph is hand-built and passes veraPDF, the official ISO 19005 validator.
- Byte-exact round-trips. Generate and parse are inverses:
generate → parse → regeneratereproduces the identical XML. 307 tests hold the line. - Schematron, local. The official EN-16931 + XRechnung rules run via saxon-js (the real XSLT, in pure JS) - no Java, no upload.
Packages
| Package | What it is |
|---|---|
| @jasy/zugferd | ZUGFeRD / XRechnung: your data → PDF/A-3 + EN-16931 XML, with local validation. The prize. |
| @jasy/cli | the jasy terminal: read · validate · export, headless and interactive |
| @jasy/pdf | the declarative, Flutter-style PDF layout engine that powers them |
Why you can trust it
- 307 tests, green. The generator and the parser are byte-exact inverses:
generate → parse → regeneratereproduces the identical XML. Nothing is silently lost. - The same rules the authorities use - the official KoSIT EN-16931 + XRechnung Schematron, and the official veraPDF for the full ISO 19005 (PDF/A) check.
- Proven on real third-party invoices, not only our own.
- 100% local. No upload, no service, no account - DSGVO-safe by construction.
Honest scope
jasy targets the documents that matter here: invoices, reports, quotes, datasheets. It is not a LaTeX / WeasyPrint replacement - no microtypography, hyphenation or bidi, and arbitrary multi-page flow of any content is still maturing. For e-invoices (a table, totals, a footer) it is complete - they even paginate. We would rather under-promise and over-deliver in the demo above.
Status: young and pre-1.0. The API can still shift between minor versions. Everything shown here works and is tested.
MIT · built by Florian Heuberger