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/e-invoice · npx @jasy/cli · MIT
npx @jasy/cli validate ./your-invoice.pdfThat ✓ 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 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/e-invoice";
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.
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 · ExcelReads CII and UBL, real third-party invoices included - not just our own output.
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 (real font metrics, kerning, ligatures, word-wrap), full flexbox (gap / justify /
align, wrap, flexBasis, flexShrink, order), boxes with per-corner radius, gradients and alpha, images and
SVG, a Canvas to draw into, custom fonts from a file, bytes or a URL, AcroForm fields,
AES-256 password encryption, tagged PDFs for screen readers, and real pagination - content that
overflows flows onto the next page, headers and footers repeat, and a block can refuse to be split.
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. Over 1,600 tests hold the line across the workspace. - Schematron, local. The official EN-16931 + XRechnung rules run via saxon-js (the real XSLT, in pure JS) - no Java, no upload.
- Vectors, not bitmaps.
Image("logo.svg")keeps a logo a VECTOR - our own SVG reader and our own XML parser, checked against 10,819 real files and compared pixel for pixel with headless Chrome. Need a chart instead?Canvashands you a typed pen for the same vector layer. - Fonts, whatever the container.
.ttf,.woffand.woff2are the same thing at the call site. The WOFF2 container and itsglyftransform are ours; only Brotli is a dependency, and it is loaded only if a WOFF2 actually turns up. - Byte-stable output. The same document rendered twice is byte-identical, PDF/A and ZUGFeRD included, across separate processes. An archived invoice stays re-derivable and hashable years later.
- Accessible if you ask.
renderToBytes(doc, { accessible })emits a full tagged structure tree, and the output passes veraPDF's machine-verifiable PDF/UA-1 checks.
| Package | What it is |
|---|---|
| @jasy/e-invoice | 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 |
| @jasy/vue | author PDFs as Vue components - and render them in the browser |
| @jasy/nuxt | the Nuxt module: zero-config PDFs, client or server |
- Over 1,600 tests, green across the workspace (1,190 in the engine alone). 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.
jasy targets the documents that matter here: invoices, reports, quotes, datasheets - and it now sets
text properly: kerning and Latin ligatures from the font's own tables, justification, pluggable
hyphenation, right-to-left and Arabic shaping, underlines that step around descenders. Pagination is
real: text splits at line boxes, keepTogether moves a card whole, orphans and widows are honoured.
It is still not a LaTeX or WeasyPrint replacement, and does not try to be: no automatic microtypography, no floats, no CSS engine. What it will not draw, it names - an SVG filter, a font without the glyph, a word that cannot be split - rather than quietly drawing something else.
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



