XRechnung & ZUGFeRD
German XRechnung and ZUGFeRD e-invoices
Copy to clipboard@normwerk/einvoice-medusa
A thin Medusa v2 plugin for German e-invoicing (XRechnung 3.0 / ZUGFeRD, EN 16931): subscribes to Copy to clipboardorder.fulfillment_created, Copy to clipboardorder.fulfillment_canceled, Copy to clipboardpayment.refunded and Copy to clipboardorder.canceled — an invoice per fulfillment, a credit note per refund or cancellation — maps the order to Copy to clipboard@normwerk/einvoice-commerce's invoice model, and stores the result — idempotently, with no duplicate documents on event redelivery. No tax logic and no XML live in this package; that's Copy to clipboard@normwerk/einvoice-commerce/Copy to clipboard@normwerk/einvoice-cii's job, kept platform-agnostic.
The seller is in Germany. What each buyer receives — the full list, with the territories refused, is in Copy to clipboarddocs/supported-countries.md:
Seller — tax rules Buyer Document Delivery Since 🇩🇪 Germany 🇩🇪 Business or consumer in Germany ZUGFeRD / Factur-X, Copy to clipboardEN 16931 profile — or XRechnung 3.0, if you prefer it File: XML, or PDF/A-3b 0.1.0 🇩🇪 Germany 🇩🇪 Public-sector buyer in Germany, with a Leitweg-ID XRechnung 3.0 File: XML, or PDF/A-3b 0.1.0 🇩🇪 Germany 🇪🇺 Any other EU member state, except Italy and Poland; Iceland, Liechtenstein and Norway (EEA) ZUGFeRD / Factur-X, Copy to clipboardEN 16931 profile File: XML, or PDF/A-3b 0.1.0 🇩🇪 Germany 🇨🇭 🇬🇧 Switzerland and the UK ZUGFeRD / Factur-X, Copy to clipboardEN 16931 profile File: XML, or PDF/A-3b 0.1.0
Not supported: 🇮🇹 Italy (SDI), 🇵🇱 Poland (KSeF) — clearance platforms with an XML of their own, which no EN 16931 document can serve; out of scope. A buyer anywhere else, and a seller outside Germany, are refused too.
A PDF/A-3 hybrid from any PDF renderer you supply (including a bundled one, Copy to clipboard@normwerk/einvoice-pdfa's Copy to clipboardrenderInvoicePdf), or XML only. The plugin numbers and issues the invoices itself: next to another plugin that issues invoices, the buyer gets two invoices for one supply — turn invoicing off in the other plugin. Documents are stored in Medusa's own File Module (private), with an admin "E-Invoices" widget on the order page and a Store API endpoint for a customer to fetch their own e-invoice. Each issued or refused document is announced on Medusa's event bus (Copy to clipboardeinvoice.document_issued, Copy to clipboardeinvoice.issuance_blocked; payload types from Copy to clipboard@normwerk/einvoice-medusa/events), and an order's documents are readable with the order (Copy to clipboardeinvoice_documents) — so a shop's own code can mail the invoice to the buyer or hand it to accounting.
Compatibility
Supported: Medusa 2.12–2.15 and 2.18 or later 2.x — the releases the end-to-end suite passes on (a real store, from checkout to a validated invoice, credit note and refund), each minor line run at its latest patch. The peer dependencies declare exactly that range, and on any other release the plugin refuses to start (Copy to clipboardUnsupportedMedusaVersionError) rather than run where it has not been shown to issue correct invoices. Need another release? A pull request is welcome, or write to hello@normwerk.dev about adapting it.
Medusa End-to-end suite Supported Note 2.21.2 41/41 yes Also the version the plugin is developed and unit-tested against 2.21.1 41/41 yes 2.21.0 41/41 yes The suite's default 2.20.1 41/41 yes 2.19.0 41/41 yes 2.18.0 41/41 yes 2.16.0, 2.17.2 — no The plugin refuses to start. Before it did, the suite passed 10 of its then 22 checks there: Medusa charged no VAT on the products in the test store, only on shipping, so every domestic order was refused. Cross-border orders passed 2.15.5 41/41 yes 2.14.2 41/41 yes npm installs Medusa 2.14.2 itself only with Copy to clipboard--legacy-peer-deps, plugin or not: Copy to clipboard@medusajs/icons@2.14.2 requires React 19, Medusa's own dashboard 2.14.2 React 18 2.13.6 41/41 yes 2.12.6 41/41 yes below 2.12 — no Not run: the test store does not start on these releases yet
Invoices and credit notes are issued for a seller in Germany only; the plugin refuses to start with any other seller country. Buyers can be in Germany (businesses, consumers, public-sector buyers), elsewhere in the EU/EEA except Italy and Poland (their clearance platforms take no EN 16931 document), Switzerland or the UK.
Versioning
Semantic versioning covers the plugin's documented interface only: its options, the events Copy to clipboardeinvoice.document_issued and Copy to clipboardeinvoice.issuance_blocked with their types in Copy to clipboard@normwerk/einvoice-medusa/events, the documents read with an order (Copy to clipboardeinvoice_documents), the Admin and Store API routes, the "E-Invoices" admin widget, the plugin's database tables, the field mapping in the mapping reference, and the dry run's script and arguments (dry run); its report carries a format version of its own. Every other file in the package can be imported — Medusa loads subscribers and routes by their path — but is internal: paths such as Copy to clipboardinvoices/* or Copy to clipboardmapping/* change in any release.
Install
1npm install @normwerk/einvoice-medusa @normwerk/einvoice-model @normwerk/einvoice-commerce @normwerk/einvoice-cii
Full setup — configuration, migrations, optional PDF modes — is the quickstart: about 30 minutes from a fresh Copy to clipboardcreate-medusa-app project to a KoSIT-validated invoice on your first fulfilled order.
Dry run: try it on your orders first
Before you turn the plugin on, run it over a period of your past orders on a staging copy — it issues nothing, and reports for every fulfillment whether it would issue the invoice, issue it with a notice, or refuse it and why:
1npx medusa exec ./node_modules/@normwerk/einvoice-medusa/.medusa/server/src/scripts/einvoice-dry-run.js from=2026-09-01 to=2026-09-30
The report names no customer. Copy to clipboardnpx @normwerk/einvoice-conformance dry-run einvoice-dry-run validates the invoices' XML with the official validators and writes it as Markdown. How to run it and how to read it: dry run.
Documentation
Part of normwerk/einvoice. See that repository for:
- Quickstart
- Dry run — the plugin over your past orders, issuing nothing
- Supported countries
- Medusa field → BT/BG mapping reference
- Storage, admin widget, Store API design
- Domain glossary — every real Medusa v2 gotcha found building this
License
MIT — see Copy to clipboardLICENSE.
