darvis / ubl-peppol
Generates UBL 2.1 invoices and credit notes that pass PEPPOL BIS Billing 3.0 and EN 16931 validation, with Dutch and Belgian rules, VIES and company registration checks. Works in plain PHP; the Laravel integration is optional.
Requires
- php: ^8.2
- ext-dom: *
- ext-libxml: *
Requires (Dev)
- larastan/larastan: ^3.0
- laravel/pint: ^1.0
- orchestra/testbench: ^9.0|^10.0|^11.0
- pestphp/pest: ^3.0|^4.0
- pestphp/pest-plugin-laravel: ^3.0|^4.0
- phpunit/phpunit: ^11.0|^12.0|^13.0
Suggests
- illuminate/support: Only for the Laravel layer (service provider, PeppolService, the PeppolLog model and the cleanup command). The invoice builders and the validator run without it.
Provides
None
Conflicts
None
Replaces
None
- dev-main
- v1.10.1
- v1.10.0
- v1.9.1
- v1.9.0
- v1.8.0
- v1.7.1
- v1.7.0
- v1.6.0
- v1.5.0
- v1.4.1
- v1.4.0
- v1.2.5
- v1.2.4
- v1.2.3
- v1.2.2
- dev-update-2026-09-issue-date-today
- dev-update-2026-09-customer-party-id-scheme
- dev-update-2026-09-nl-credit-note
- dev-update-2026-09-correct-generated-values
- dev-update-2026-09-docs-accuracy
- dev-update-2026-09-test-connection-and-credit-note-docs
- dev-update-2026-09-optional-log-and-build-order
- dev-update-2026-09-social-image
- dev-update-2026-09-package-skeleton
- dev-development
- dev-credit-be-20260211
This package is auto-updated.
Last update: 2026-09-21 13:41:48 UTC
README
A PHP library that builds UBL 2.1 e-invoices for PEPPOL BIS Billing 3.0, the format the PEPPOL network requires (built on EN 16931). It has one builder for Dutch and one for Belgian invoices and credit notes. The builders are plain PHP; an optional Laravel layer posts the XML to your access point provider.
Features
- Dutch invoices and credit notes with
UblNlBis3Service, which puts the elements in schema order for you - Belgian invoices and credit notes with
UblBeBis3Service, which can add up the lines withcalculateTotals() validate()before sending: code formats and the Dutch NL-R rules in the Dutch builder, the totals (BR-CO-10, 13, 15, 16, BR-S-08) in the Belgian builderViesServicechecks a European VAT number against VIES and tells "invalid" apart from "VIES did not answer"CompanyRegistrationServicechecks the format of a KvK, KBO, RCS, SIREN/SIRET or Handelsregister number- In Laravel:
PeppolServiceposts the XML to your provider, with an optionalpeppol_logstable and apeppol:cleanupcommand
It is not an access point, and validate() is not the full Schematron a receiver runs: check a document with an official validator before you go live.
Requirements
- PHP 8.2 or newer with the
domandlibxmlextensions - The
bcmathextension to check an IBAN, thesoapextension forViesService - Laravel 11, 12 or 13, only for the optional Laravel layer
Installation
composer require darvis/ubl-peppol
In Laravel the service provider registers itself. To send invoices, set PEPPOL_URL, PEPPOL_USERNAME and PEPPOL_PASSWORD in .env. The config file and the log table are optional:
php artisan vendor:publish --tag=ubl-peppol-config php artisan vendor:publish --tag=ubl-peppol-migrations
Quick start
<?php // invoice.php require __DIR__.'/vendor/autoload.php'; 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( '12345678', '0106', '12345678', 'My Dutch Company BV', 'Damrak 1', '1012 JS', 'Amsterdam', 'NL', 'NL123456789B01' ); $ubl->addSupplierLegalRegistration('12345678'); // your KvK number, scheme 0106 $ubl->addAccountingCustomerParty( '87654321', '0106', '87654321', 'Customer Company BV', 'Nieuwezijds Voorburgwal 123', '1012 RJ', 'Amsterdam', 'NL', null, '87654321', null, null, null, 'NL987654321B01' ); $ubl->addPaymentMeans('30', 'Credit transfer', 'INV-2026-001', 'NL91ABNA0417164300', null, 'ABNANL2A'); $ubl->addPaymentTerms('Payment within 30 days'); $ubl->addTaxTotal([[ 'taxable_amount' => 425.00, 'tax_amount' => 89.25, 'currency' => 'EUR', 'tax_category_id' => 'S', 'tax_percent' => 21.0, 'tax_scheme_id' => 'VAT', ]]); $ubl->addLegalMonetaryTotal([ 'line_extension_amount' => 425.00, 'tax_exclusive_amount' => 425.00, 'tax_inclusive_amount' => 514.25, 'charge_total_amount' => 0.00, 'payable_amount' => 514.25, ], 'EUR'); $ubl->addInvoiceLine([ 'id' => '1', 'quantity' => 5, 'unit_code' => 'HUR', 'price_amount' => 85.00, 'currency' => 'EUR', 'name' => 'Software development', 'description' => 'Frontend development, 5 hours', 'tax_category_id' => 'S', 'tax_percent' => 21.0, ]); // Throws an InvalidArgumentException when validate() finds an error. file_put_contents(__DIR__.'/invoice.xml', $ubl->generateXml(validateFirst: true));
Use UblBeBis3Service when the receiver is Belgian. Create a new builder for every document. The documentation explains every argument.
Documentation
Full documentation at arviddejong.github.io/ubl-peppol:
- Installation: requirements, configuration and a check that it works
- Your first invoice: the example above, line by line
- Dutch invoices and Belgian invoices: the fields of every call and what each builder checks
- Credit notes: with either builder
- Validation: what
validate()checks and how to read the result - VAT numbers and company numbers
- Laravel integration and sending invoices
- Testing:
Http::fake(), with and without the log table - API reference, troubleshooting and the FAQ
Runnable examples for both countries are in examples/.
Laravel Boost
The package ships a Laravel Boost guideline and skill in resources/boost/, so an AI assistant in your project knows how the builders work. Run php artisan boost:install, or php artisan boost:update --discover in a project that already uses Boost.
Testing
composer test # Pest composer lint # Pint, check only; composer format fixes composer analyse # Larastan
Changelog
See CHANGELOG.md.
Contributing
See CONTRIBUTING.md.
Security
Found a way to inject XML through invoice data, or another vulnerability? Please report it privately; see SECURITY.md.