Version 1.11: VAT categories as a knowledge base
An enum with all nine VAT categories PEPPOL allows, explaining when to use which and which exemption reason belongs to it. validate() now also checks what each category requires.
darvis/ubl-peppol
A PHP package that builds UBL 2.1 invoices and credit notes following PEPPOL BIS Billing 3.0 and EN 16931. With Dutch and Belgian rules, a VAT number check through VIES and an optional Laravel integration.
<Invoice xmlns="urn:oasis:...:Invoice-2"> <cbc:CustomizationID>urn:cen.eu:en16931:2017 #compliant#urn:fdc:peppol.eu:2017:poacc :billing:3.0</cbc:CustomizationID> <cbc:ID>INV-2026-001</cbc:ID> <cbc:IssueDate>2026-01-15</cbc:IssueDate> <cac:TaxTotal> <cbc:TaxAmount currencyID="EUR">89.25</cbc:TaxAmount> </cac:TaxTotal> ... </Invoice>
You already have your invoice data, in a database, an ERP or a web shop. This package turns it into an XML document a PEPPOL receiver accepts. You map your data to the builder, the package writes the XML.
New to Peppol? First read what Peppol is and how the network works, or get in touch if you want help with your integration.
An enum with all nine VAT categories PEPPOL allows, explaining when to use which and which exemption reason belongs to it. validate() now also checks what each category requires.
Upload or paste a UBL invoice or credit note and check it against the official EN 16931 and PEPPOL BIS Billing 3.0 rules. Everything runs in your browser; nothing is uploaded.
Since version 1.10 the Dutch builder also creates credit notes, with a reference to the invoice you correct.
Create a builder, add the header, the parties, the payment, the VAT, the totals and the lines, and let it write the XML.
With validateFirst: true you know before sending whether anything is missing.
use Darvis\UblPeppol\UblNlBis3Service;
$ubl = new UblNlBis3Service();
$ubl->createDocument();
$ubl->addInvoiceHeader('INV-2026-001', '2026-01-15', '2026-02-14');
$ubl->addBuyerReference('CLIENT-001');
$ubl->addAccountingSupplierParty(/* your company */);
$ubl->addAccountingCustomerParty(/* your customer */);
$ubl->addPaymentMeans('30', 'Credit transfer', 'INV-2026-001', 'NL91ABNA0417164300');
$ubl->addTaxTotal([/* VAT per category */]);
$ubl->addLegalMonetaryTotal([/* totals */], 'EUR');
$ubl->addInvoiceLine([/* invoice line */]);
// Throws an InvalidArgumentException when validate() finds an error.
$xml = $ubl->generateXml(validateFirst: true);
Since 1 January 2026, Belgian companies must invoice each other electronically. Peppol is the default channel for this.
The Dutch government wants to make electronic invoicing between businesses mandatory from 1 July 2030. Connect now and you are ready in time.
ViDA brings digital VAT reporting and electronic invoices for trade between businesses across borders.
No. The package is not an access point. PEPPOL is a closed network you reach through an access point provider you have a contract with. In Laravel, PeppolService posts the XML to that provider's API, using PEPPOL_URL, PEPPOL_USERNAME and PEPPOL_PASSWORD from your .env.
Pick the builder by the receiver's country. UblNlBis3Service puts the elements in the right order itself and checks the Dutch rules. UblBeBis3Service adds up the lines for you with calculateTotals() and checks that the amounts add up. Both build credit notes.
No. validate() changes nothing in your document and checks the rules the package knows. A receiver checks more. So run a document through an official validator before you go live, for example the online validator in the documentation.
PHP 8.2 or newer with the dom and libxml extensions. You need bcmath to check an IBAN and the soap extension for VIES. Laravel 11, 12 or 13 is only needed for the optional Laravel layer.
Yes. The package is free and released under the MIT license. You may use it in commercial projects.