Skip to content
PN Scripts

Development

EN 16931 e-invoices in PHP: UBL vs CII and validation

  • PN Scripts Team
  • 9 min read

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.

Keep reading

Keep reading

Comments

Comments

Be the first to leave a comment.

Leave a comment

Next step

pnscripts.com/contact

Talk to the team

Ask about an article, or tell us about a project you want built. We reply within one business day.

Write to us

The PN Scripts family

Other PN Scripts sites

Hosting, games and the blog each have their own site, run by the same company.

  • pnscripts.com

    PN Scripts

    Software engineering

    Custom web, mobile, API and game development, plus our open-source products and plugins.

  • games.pnscripts.com

    Games

    Games and game servers

    The home for PN Scripts games and game servers. The catalog is empty for now and fills up as titles and servers go live.

  • hosting.pnscripts.com

    Hosting

    Hosting and infrastructure

    Shared hosting, KVM VPS, dedicated servers and domains, from the same company that builds your project.

  • blog.pnscripts.com

    Blog

    Articles and field notes

    Plain articles on hosting, servers, domains and security, written by the people who work with them.

    You are here