Documentation

Profiles

Two small decisions, both with a default you rarely change: which conformance profile (ZUGFeRD or XRechnung) and which XML syntax (CII or UBL). The same Invoice feeds all of them - you just pick.

ZUGFeRD or XRechnung?

renderZugferd(invoice); // ZUGFeRD (en16931) - the default
renderZugferd(invoice, { profile: "xrechnung" }); // German B2G

ZUGFeRD (en16931) is the default and what most invoices want: standard EN-16931, works for B2B across Europe. Nothing to set.

XRechnung (xrechnung) is for selling to a German public authority (B2G), where it is mandatory. It is EN-16931 plus stricter German rules - a few extra fields are required:

  • the Leitweg-ID in buyerReference (the authority gives you this),
  • an electronic address on both the seller and the buyer,
  • a full seller contact (name, phone, email) and city + post code,
  • a due date or payment terms, and an IBAN for a credit transfer.

Here is the reassuring part: you do not have to memorise that list. Choose the profile and the library pre-checks it, throwing one clear, field-named error if anything is missing - long before a portal ever rejects it.

// Missing the Leitweg-ID? You get told exactly that, with the field to set:
// Error: Invoice is not XRechnung-ready:
//   - XRechnung needs the Leitweg-ID - set invoice.buyerReference (BT-10).
await renderZugferd(invoice, { profile: "xrechnung" });

The extra fields a complete XRechnung needs, in one place:

const invoice: Invoice = {
  // ... the usual fields ...
  buyerReference: "04011000-12345-34", // Leitweg-ID (BT-10)
  dueDate: "2026-07-05",
  seller: {
    name: "Northwind GmbH",
    vatId: "DE265013614",
    electronicAddress: "billing@northwind.de", // BT-34
    contact: { name: "A. Schmidt", phone: "+49 30 123456", email: "billing@northwind.de" },
    address: { line1: "Hauptstrasse 1", city: "Berlin", postCode: "10115", country: "DE" },
  },
  buyer: {
    name: "Bundesamt fuer Beschaffung",
    electronicAddress: "einkauf@behoerde.de", // BT-49
    address: { city: "Bonn", postCode: "53113", country: "DE" },
  },
  payment: { iban: "DE89370400440532013000", terms: "Payable within 30 days." },
  // ... lines ...
};

You can also run the check yourself, without rendering, to gate a form or a save button:

import { xrechnungProblems } from "@jasy/zugferd";

const problems = xrechnungProblems(invoice); // string[] - empty means good to go

CII or UBL?

EN-16931 has two interchangeable XML syntaxes. You almost never have to think about this.

CII (Cross Industry Invoice) is the ZUGFeRD / Factur-X standard, and it is what renderZugferd embeds and returns in .xml. That is the default and what you want.

const { bytes, xml } = await renderZugferd(invoice); // xml is the CII

Need the XML on its own - say, a portal that wants a file upload - or specifically UBL (Universal Business Language, the other EN-16931 syntax some systems prefer)? Compute once, then convert:

import { computeInvoice, toCII, toUBL } from "@jasy/zugferd";

const computed = computeInvoice(invoice);
const cii = toCII(invoice, computed); // same XML renderZugferd embeds
const ubl = toUBL(invoice, computed, "xrechnung"); // UBL, XRechnung CIUS

Both accept the profile ("en16931" default, or "xrechnung"), so the guideline identifier inside the XML matches what you are filing.

jasypdf

Declarative PDFs in pure TypeScript. ZUGFeRD & XRechnung compliant, with no headless browser and no Java.

Resources

© 2026 Florian Heuberger · MIT License

Built with Nuxt · self-hosted fonts · no trackers