Factur-X vs ZUGFeRD vs UBL vs Peppol: a developer's map
E-invoicing's most-confused names don't compete — they live at four different layers: semantic model, XML syntax, PDF packaging, delivery network. A working map for developers, the 2026 mandate landscape it has to fit, and the one architecture that serves all of it.
You've been asked to "add e-invoicing support", and twenty minutes of searching has produced a pile of acronyms that all seem to mean "electronic invoice": Factur-X, ZUGFeRD, UBL, CII, Peppol, EN 16931, XRechnung, BIS Billing. Half the articles compare them as if you must pick one. That framing is why the topic feels impenetrable — these are not four competing formats. They are names from four different layers. Comparing Factur-X to Peppol is comparing a PDF file to a postal service.
We build e-invoicing in PHP for a production invoicing product, and this is the map we wish someone had drawn before we read four specifications to reverse-engineer it. Four layers, top to bottom:
- EN 16931 — the semantic model: which fields an invoice carries and the rules between them. Everything else implements this.
- CII and UBL — the two XML syntaxes that can express that model.
- Factur-X / ZUGFeRD — a packaging: a human-readable PDF with the CII XML embedded inside. One file, two audiences.
- Peppol — a delivery network with its own profile of extra rules. It's how invoices travel, not what they are.
Layer by layer, with the current versions and the mandates attached.
The semantic model: EN 16931
The European standard behind all of it. EN 16931 defines the invoice as
a list of numbered business terms — BT-1 is the invoice number, BT-9
the due date, BG-23 a VAT breakdown group — plus the business rules
(BR-*) that tie them together: totals must reconcile, a reverse-charge
invoice must carry both parties' VAT numbers, an exemption needs its
reason. Not XML. Just semantics, the way a database schema isn't a file
format.
This layer is where your code should live, and it's also where the EU is converging: the ViDA package, adopted 11 March 2025, makes EN 16931 e-invoicing the rule for intra-EU B2B transactions from 1 July 2030, with national systems required to align by 2035. The standard itself was refreshed in March 2026 (EN 16931-1:2026) to carry ViDA's reporting fields. Learn the BT-numbers; they are the vocabulary every validator error message and every national spec is written in.
The two syntaxes: CII vs UBL
The same semantic invoice can be serialized into two different XML dialects, and "which is better" is the wrong question — the receiving side decides which one you emit.
CII (UN/CEFACT Cross Industry Invoice) is the syntax Factur-X and ZUGFeRD embed, which makes it the default in the Franco-German world. The invoice number and type look like this:
<rsm:ExchangedDocument>
<ram:ID>FA-2026-0042</ram:ID> <!-- BT-1 -->
<ram:TypeCode>380</ram:TypeCode> <!-- BT-3: 380 invoice, 381 credit note -->
</rsm:ExchangedDocument>
UBL 2.1 (OASIS, standardized as ISO/IEC 19845) is the syntax the Peppol network carries, which makes it the default in the Nordics, the Benelux, and most of the world outside France and Germany. The same two terms:
<Invoice xmlns="urn:oasis:names:specification:ubl:schema:xsd:Invoice-2">
<cbc:ID>FA-2026-0042</cbc:ID> <!-- BT-1 -->
<cbc:InvoiceTypeCode>380</cbc:InvoiceTypeCode> <!-- BT-3 -->
</Invoice>
Same BT-1, same BT-3, different trees — and that's the entire relationship between the two: every EN 16931 term has a defined home in both syntaxes. Model your invoice against the terms and each syntax is a serializer; model it against one syntax and the other becomes a rewrite. This one architectural decision is most of the battle, and we wrote up the generation side of it in Generate Factur-X and ZUGFeRD invoices in PHP.
The hybrid packaging: Factur-X and ZUGFeRD
One standard, two flags. Factur-X is the French name (maintained by FNFE-MPE), ZUGFeRD the German one (FeRD) — same specification, jointly released, byte-compatible. The current aligned version is Factur-X 1.08 / ZUGFeRD 2.4, in force since 15 January 2026 (joint press release); it stays backward-compatible with the widely deployed 2.3.x line.
The idea is the layer cake's clever trick: take the PDF you already
render for humans, upgrade it to PDF/A-3 (the archival profile that
allows attachments), and embed the CII XML inside as factur-x.xml.
Accountants read the page; platforms extract the XML; you ship one file
and both audiences are served. No portal, no network membership — it
travels over plain email.
Two traps worth flagging:
- Profiles. Factur-X comes in MINIMUM, BASIC WL, BASIC, EN 16931 and EXTENDED profiles. Only from the EN 16931 profile up does the XML carry the full semantic model — MINIMUM is a processing aid, not a valid invoice under the French or German mandates. Aim for EN 16931.
- Hybrid means two sources of truth. The XML is the legal invoice; the PDF is a rendering. If your pipeline lets them diverge (a discount on the page missing from the XML), validators won't catch it — only your generator discipline will: render both from the same document model.
The network: Peppol
Peppol is the one that is genuinely not a format. It's a delivery network: a four-corner model where you hand your invoice to your access point, which routes it to the receiver's access point, discovered via participant identifiers in a central directory. Think SMTP for invoices, with contracts.
What travels over it is UBL — but constrained by Peppol BIS Billing
3.0, a profile that adds its own Schematron rules on top of EN 16931.
The additions are exactly where real documents get rejected: a buyer
reference or PO reference becomes mandatory (rule R003), both parties
must carry an electronic address with a scheme from the EAS code list
(R010/R020). A perfectly valid EN 16931 UBL invoice can be invalid
as a Peppol document — keep the standard's rules and the profile's
rules separate in your validator, because a domestic B2C invoice must
not be forced through Peppol's stricter gate. The full CI setup that
enforces both rule sets — official artefacts, pinned versions — is in
Validate EN 16931 e-invoices in CI.
Beyond Europe, Peppol is becoming the default rail with a variant model: PINT, the Peppol International invoice, live on the network since July 2023. Japan's JP PINT, the shared PINT A-NZ (the only specification Australian and New Zealand access points accept since November 2024), Singapore's InvoiceNow framework, Malaysia's rollout — all Peppol, all PINT-based (checked 6 July 2026 on peppol.org). If "international customers" is in your roadmap, the network half of this map is not optional reading.
The mandate map, mid-2026
What actually forces the issue — who requires what, checked 6 July 2026:
Germany
Every business has been required to accept EN 16931 e-invoices since January 2025; mandatory issuance phases in during 2027 (companies above €800k turnover) and 2028 (everyone). ZUGFeRD hybrids and XRechnung (the German CIUS — a national tightening of EN 16931, mandatory for invoicing public bodies) both satisfy it.
France
From 1 September 2026, every business must be able to receive e-invoices, and large and mid-size companies must issue them; SMEs follow on 1 September 2027. Exchange runs through accredited platforms (plateformes agréées, the former PDPs), and the reform accepts three formats — UBL, CII, or Factur-X (official overview). The hybrid is the expected default for anything a human also reads.
Belgium
The one already live: since 1 January 2026, structured e-invoices are mandatory for domestic B2B, exchanged over Peppol with BIS Billing 3.0 as the default profile. The initial three-month penalty tolerance has expired; fines start at €1,500.
Poland (the counter-example)
Not every mandate speaks EN 16931. Poland's KSeF is a central clearance platform with a national XML schema, FA(3) — mandatory since 1 February 2026 for large taxpayers and 1 April 2026 for the rest. Italy's FatturaPA/SDI (running since 2019) is the same pattern. Selling into clearance countries means one more serializer against your document model — not a new engine.
So what do you build?
The map collapses into a short set of decisions, and they're the same for a SaaS emitting its own invoices or a product doing it for customers:
- Model once, against EN 16931. An immutable document model whose fields map to BT-terms, with amounts in integer cents. Every format below this line is a view.
- Serialize per destination. France/Germany, humans in the loop → CII embedded in PDF/A-3 (Factur-X). Peppol countries or public-sector buyers → UBL with the BIS profile. Clearance countries → their schema, when you need them.
- Validate per layer in CI. Syntax by XSD, EN 16931 rules and profile rules by the official Schematron artefacts — separately, so Peppol's extra strictness never blocks a valid domestic hybrid.
- Treat transport as a provider integration. PDF/A-3 embedding is one class; Peppol access is something you buy from an access point, not something you build.
Do it in that order and the acronyms stop being scary: they're one model, two syntaxes, one envelope and one postal service.
The engine behind these posts runs in production, validated against the official rule sets on every merge. If you're building a Symfony SaaS and want the foundations underneath such an engine already in place — auth with 2FA, teams, billing on two providers, an admin, a CI culture that makes "blocking Schematron job" a natural sentence — that's what ShipAnvil ships. Start with the live demo, then go make BT-1 mean something.