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.