Documentation

The Invoice model

Afraid of the official paperwork? Don't be. EN-16931 defines a long list of fields, but you need a handful. Fill in the few that are mandatory, add detail only where your case calls for it, and the library computes every total and writes the conformant XML underneath. Each field maps to a real business term (its BT- / BG- code lives in the type, for when you ever need it).

The smallest invoice

This is already a complete, conformant invoice. Everything else on this page is optional.

import { type Invoice } from "@jasy/zugferd";

const invoice: Invoice = {
  number: "INV-001",
  issueDate: "2026-06-21", // ISO date
  currency: "EUR",
  seller: {
    name: "Northwind GmbH",
    vatId: "DE265013614",
    address: { city: "Berlin", postCode: "10115", country: "DE" },
  },
  buyer: {
    name: "Globex Ltd",
    address: { city: "Munich", postCode: "80331", country: "DE" },
  },
  lines: [
    {
      name: "Consulting",
      quantity: 8,
      unit: "HUR",
      netUnitPrice: 120,
      vat: { category: "S", ratePercent: 19 },
    },
  ],
};

number, issueDate, currency, seller, buyer and at least one line - that is the whole requirement. Hand it to renderZugferd and out come the PDF and the XML.

Seller and buyer

Both parties take the same shape. A name and an address (only the country is required) are mandatory; everything else is opt-in.

seller: {
  name: "Northwind GmbH", // registered legal name
  vatId: "DE265013614", // needed whenever you charge VAT
  address: { line1: "Hauptstrasse 1", city: "Berlin", postCode: "10115", country: "DE" },
  contact: { name: "A. Schmidt", email: "billing@northwind.de", phone: "+49 30 123456" },
  electronicAddress: "billing@northwind.de", // required for XRechnung / Peppol
},

Other handy fields: tradingName, taxNumber (Steuernummer), legalRegistrationId (Handelsregisternummer) and additionalLegalInfo (the legal footer line). The buyer mirrors all of it.

Lines

Each line names an item, a quantity in a unit, the net unit price and its VAT. The library multiplies and sums - you never add up a line yourself.

lines: [
  {
    name: "Consulting",
    description: "On-site workshop", // optional
    quantity: 8,
    unit: "HUR", // UN/ECE Rec 20 - "HUR" hours, "C62" pieces, "DAY", "KGM" kg, "MTR" metre ...
    netUnitPrice: 120,
    vat: { category: "S", ratePercent: 19 },
  },
  {
    name: "License",
    quantity: 1,
    unit: "C62",
    netUnitPrice: 480,
    vat: { category: "S", ratePercent: 19 },
  },
],

More per line when you need it: sellerItemId / buyerItemId / standardItemId (a GTIN), priceBaseQuantity (a price "per 100"), per-line allowancesCharges, and a free-text note.

VAT

VAT sits on the line (and on any allowance or charge): a category plus its rate.

CategoryMeaningRate
SStandard ratethe percent, e.g. 19
ZZero-rated0
EExempt from VATomit
AEReverse chargeomit
KIntra-community supply (EU)omit
GExport outside the EUomit
OOutside the scope of VATomit

For the non-standard categories EN-16931 wants a reason. Give it once per category:

const invoice: Invoice = {
  // ... every line uses { category: "AE" } ...
  vatExemptionReasons: {
    AE: {
      text: "Reverse charge - Steuerschuldnerschaft des Leistungsempfängers (§13b UStG)",
      code: "VATEX-EU-AE",
    },
  },
};

The optional extras

Reach for these as your case calls for them. None are required.

Dates and references

dueDate: "2026-07-05",
buyerReference: "04011000-12345-34", // the Leitweg-ID - MANDATORY for XRechnung (B2G)
purchaseOrderRef: "PO-7782",
contractRef: "C-2026-09",
notes: ["Thank you for your business."],

Payment - so the buyer knows where and how to pay:

payment: {
  meansCode: "58", // SEPA credit transfer (UNCL 4461); "30" is a plain credit transfer
  iban: "DE89370400440532013000",
  bic: "COBADEFFXXX",
  accountName: "Northwind GmbH",
  terms: "Payable within 14 days, net.",
},

Document-level discounts or charges - applied after the line sum, each with its own VAT:

allowancesCharges: [
  { isCharge: false, amount: 50, reason: "Loyalty discount", vat: { category: "S", ratePercent: 19 } },
  { isCharge: true, amount: 12, reason: "Shipping", vat: { category: "S", ratePercent: 19 } },
],

And a few more: delivery (a date and a deliver-to address), payeeName (when payment goes to someone other than the seller), paidAmount (already paid, subtracted to give the amount due) and type (380 a commercial invoice, 381 a credit note).

That is the whole model. Start from the smallest invoice and add only what you need - the totals and the XML stay correct underneath, every time.

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