E-invoicing in the EU is moving from public procurement into everyday business. Belgium made structured B2B e-invoices mandatory at the start of 2026, France started on 1 September 2026, Germany's issuing transition ends in 2027 and 2028, and Poland's KSeF became mandatory in stages during 2026. If you write PHP for a shop, an ERP or a billing system, you will be asked to produce invoices in the European standard EN 16931 and to make sure they are valid before they leave.
This article covers the timeline, the difference between the two allowed syntaxes (UBL and CII), the layers of validation and the business-rule errors that come up most. It is written for developers. It is not legal or tax advice: what goes on an invoice and how VAT is treated depends on your country and your business.
#The timeline, country by country
Dates below come from the official government pages listed in the sources, checked on 5 October 2026. Rules change, so check again before you plan a release.
| Country | What applies | Dates |
|---|---|---|
| Belgium | Structured e-invoices between VAT-registered Belgian businesses | Since 1 January 2026; the government announced a tolerance period for the first three months of 2026 |
| France | Receiving e-invoices: all businesses. Issuing and e-reporting: large and mid-size businesses | 1 September 2026 |
| France | Issuing and e-reporting: small and micro businesses | 1 September 2027 |
| Germany | Receiving EN 16931 e-invoices: all domestic businesses | Since 1 January 2025 |
| Germany | Paper or other formats still allowed for issuing (with recipient consent) | Until 31 December 2026; until 31 December 2027 for businesses with prior-year turnover up to EUR 800,000 |
| Poland | KSeF for large businesses, and for receiving invoices | 1 February 2026 |
| Poland | KSeF for all other businesses, except the smallest | 1 April 2026 |
| Poland | KSeF for businesses with monthly sales up to PLN 10,000 | 1 January 2027 |
At EU level, the VAT in the Digital Age package (Council Directive (EU) 2025/516) introduces e-invoicing based on EN 16931 and digital reporting for intra-EU business transactions, applying from 1 July 2030.
Two practical notes. Germany accepts XRechnung and ZUGFeRD from version 2.0.1, except the MINIMUM and BASIC-WL profiles, as formats meeting EN 16931. Poland's KSeF uses its own national XML structure, FA(3), not UBL or CII, so an EN 16931 library alone does not cover it.
#What EN 16931 is
EN 16931-1 is a semantic data model: a list of business terms (BT-1 invoice number, BT-2 issue date, BT-109 total without VAT and so on), groups of terms (BG-25 invoice line) and business rules (BR-*) that say which terms are required and how amounts must add up. It does not prescribe XML by itself. CEN/TS 16931-2 lists the syntaxes that can carry the model, and the Commission published that list in Implementing Decision (EU) 2017/1870:
- UN/CEFACT Cross Industry Invoice (CII), XML schemas D16B
- OASIS UBL 2.1, Invoice and CreditNote
Countries and networks add CIUS specifications on top: stricter or more specific rules within the standard. XRechnung (Germany) and Peppol BIS Billing 3.0 are the best-known examples.
#UBL 2.1 vs CII D16B
Both carry the same data. The differences are in structure and in where each one is used.
| UBL 2.1 | CII D16B | |
|---|---|---|
| Root element | Invoice or CreditNote (two document types) |
rsm:CrossIndustryInvoice (one document type; the type code tells invoice from credit note) |
| Structure | Fairly flat; parties and totals near the top | Nested into exchanged document, trade agreement, delivery and settlement blocks |
| Typical use | Peppol network, many public portals | Factur-X / ZUGFeRD (XML embedded in a PDF/A-3), common in France and Germany |
| XRechnung | Supported | Supported |
If your recipients are on Peppol, you will produce UBL. If customers want a PDF that people can read and machines can process, Factur-X or ZUGFeRD with CII inside is common. Many systems need both, which is a good reason to keep one internal model and write either syntax from it, rather than building XML by string concatenation in two places.
#The three layers of validation
An invoice can fail at several levels, and each needs a different tool.
1. XML that is well-formed and safe to parse. Invoices often come from outside, so treat them as untrusted input. Refuse DOCTYPE declarations and never let the parser load external entities or fetch anything over the network. In PHP, load with LIBXML_NONET and reject any document that contains a DOCTYPE before going further.
2. XML Schema (XSD). Checks structure and data types against the official UBL 2.1 or CII D16B schemas: element names, order, required elements, date and number formats. PHP can do this with DOMDocument::schemaValidate(). Passing XSD means the document has the right shape; it says nothing about whether the totals add up.
3. EN 16931 business rules. The official validation artefacts, maintained for CEN/TC 434 in the ConnectingEurope/eInvoicing-EN16931 repository, express the BR- rules as Schematron*. Running them needs an XSLT 2.0 processor such as Saxon; PHP's own ext-xsl supports only XSLT 1.0. A CIUS like XRechnung or Peppol BIS adds its own Schematron on top.
Only an invoice that passes all three layers, plus the CIUS rules of the recipient's network, is ready to send.
#The BR-* errors you will meet most
These come up again and again when generating invoices from shop or ERP data:
- BR-CO-10: the sum of line net amounts (BT-106) does not equal the sum of the invoice lines (BT-131). Usually caused by rounding each line differently from the total.
- BR-CO-13 and BR-CO-15: total without VAT, and total with VAT, do not equal the parts they are built from. Often a sign that allowances or charges at document level were left out of one calculation.
- BR-CO-14 and BR-CO-17: the VAT total does not equal the sum of the VAT breakdown, or a breakdown amount is not the taxable amount times the rate, rounded to two decimals.
- BR-S-08: for standard-rated VAT, the taxable amount per rate in the breakdown does not match the lines, allowances and charges at that rate.
- BR-E-10 and BR-AE-10: an exempt or reverse-charge VAT breakdown without an exemption reason code or text.
- BR-CO-09: a VAT identifier without a valid country prefix (Greece uses EL).
- BR-CO-25: a positive amount due without a payment due date or payment terms.
- BR-16: an invoice with no lines.
Most of the calculation errors disappear if you never store totals separately: derive the VAT breakdown and totals from the lines, using exact decimal arithmetic (bcmath or a decimal library), never floats.
#Example: PN Invoice
PN Scripts makes PN Invoice, a free, MIT-licensed, framework-agnostic PHP library for this job. It is on GitHub and Packagist as pnscripts/pn-invoice, and needs PHP 8.3 or newer with the dom, libxml and bcmath extensions.
composer require pnscripts/pn-invoice
What it does, against the layers above:
- A typed invoice and credit note model with exact decimals. Totals and the VAT breakdown are always derived from lines, allowances and charges, so the BR-CO calculation rules hold by construction.
- Writers for UBL 2.1 and CII D16B from the same model.
- A layered validator: XML safety (no network access, DOCTYPE refused), XSD with the official schemas bundled for offline use, and a documented set of 104 EN 16931 business rules in pure PHP. Each finding has a rule id, severity, layer, message and XPath.
- An optional adapter that runs the official Schematron through an external XSLT 2.0 processor, for full conformance checks.
- A command-line check:
vendor/bin/pn-invoice validate invoice.xml.
use Pnscripts\Invoice\Validation\InvoiceValidator;
$result = (new InvoiceValidator())->validateFile('invoice.xml');
foreach ($result->errors() as $violation) {
printf("%s [%s] %s at %s\n", $violation->ruleId, $violation->layer->value, $violation->message, $violation->location);
}
Please read the limits too. PN Invoice covers a documented subset of EN 16931: the README lists the rules that are implemented and those that are not yet, such as some VAT category groups and decimal and code-list rules. It does not yet include XRechnung, Factur-X or Peppol-specific rules, and it does not produce KSeF FA(3). Passing its checks does not certify that an invoice is legally compliant. It is a technical tool, not legal or tax advice; have your invoices checked by your accountant.
Requirements and the latest version are on the PN Invoice page. See also: What a reliable API integration needs.
#Frequently asked questions
Should I use UBL or CII?
Use what your recipients and their network expect. Peppol uses UBL; Factur-X and ZUGFeRD embed CII in a PDF. Both are allowed syntaxes of EN 16931, so a single internal model that can write either keeps your options open.
Is XSD validation enough?
No. XSD checks the structure and data types. The EN 16931 business rules, such as BR-CO-15 for totals, are checked with the official Schematron or an equivalent implementation, and a CIUS such as XRechnung adds more rules.
Can PHP run the official Schematron?
Not with the built-in XSL extension, which supports only XSLT 1.0, while the official artefacts need XSLT 2.0. The usual approach is to call an external XSLT 2.0 processor such as Saxon, or a validation service, and parse its SVRL report.
Does an EN 16931 library cover Poland's KSeF?
Not by itself. KSeF uses Poland's national FA(3) XML structure, so you need a separate writer and the KSeF interface.
Is PN Invoice certified?
No. It implements a documented subset of the EN 16931 business rules and is tested against the official test cases for the rules it implements. For full conformance, run the official Schematron through its adapter. It is not legal or tax advice.
Comments
Comments
Be the first to leave a comment.
Leave a comment