Belgium (quittio/country-be)
Not reviewed by a tax adviser yet. The legal wording, rules and defaults of this pack follow the law and the official sources as of October 2026, but have not been reviewed by a Belgian tax adviser. Check the mentions and rates of your invoices before you rely on them. Quittio is no tax or legal advice; the business that issues an invoice remains responsible for it.
The Belgian pack implements the invoice mentions of the Belgian VAT Code (Wetboek van de belasting over de toegevoegde waarde / Code de la TVA) and of Royal Decree No. 1 (Koninklijk besluit nr. 1, "KB nr. 1" / Arrêté royal n° 1, "A.R. n° 1"), the e-invoicing obligation of 2026 with Peppol BIS Billing 3.0, the use of languages in the language regions, and the law on late payment of 2 August 2002. Its class is Quittio\Country\BE\CountryBE (code BE, translation namespace quittio-be). Its texts exist in Dutch, French, German and English.
- Legal references. This page writes Belgian legal references in English form: "Art. 5 § 1, 10° Royal Decree No. 1" is article 5, paragraph 1, point 10 of the decree. Values that the pack stores or prints keep their Dutch or French form, e.g. "KB nr. 1, art. 20".
- Business terms. Codes such as BT-31 are the business terms of EN 16931, the European standard for e-invoices (E-invoicing basics).
Installation#
composer require quittio/country-beThe pack is not part of quittio/quittio. Install it next to quittio/quittio (with Laravel) or next to quittio/core (without Laravel). It is developed in packages/country-be of the monorepo and published as the read-only repository Quittio/country-be; open issues and pull requests in the monorepo.
- Laravel. The pack declares itself in its
composer.jsonunderextra.quittio.country-packs, so Laravel finds it after the installation; there is nothing to register (Registering packs: Laravel). The pack has no service provider and no configuration file of its own. Its one setting goes intoconfig/quittio.php(Settings). - Without Laravel. Pass the pack to
Quittio::create():
use Quittio\Core\Quittio;
use Quittio\Country\BE\CountryBE;
$quittio = Quittio::create(['country' => 'BE'], [new CountryBE]);'country' => 'BE' makes Belgium the default country for invoices whose seller has no country code. With several packs, pass them all, e.g. [new CountryBE, new CountryDE], or add one later with $quittio->registerCountry(new CountryBE) (Registering packs: Without Laravel).
When it applies#
The pack leads every invoice whose seller is in Belgium (seller country code BE), or that is rendered with ->country('BE'). It then checks the Belgian rules, chooses Peppol BIS for domestic business invoices, completes the Peppol endpoints and provides the defaults: the locale nl_BE (unless the invoice or the configuration sets one), A4 paper and the payment QR code.
For an invoice from another country to a buyer in Belgium, the pack only registers its translations. It has no rules for the buyer side (buyerCountryRules()), and its format rule applies only when the seller is in Belgium as well. How Quittio picks the leading pack and the buyer pack is described in How a pack is selected.
Common tasks#
| You want to | Read |
|---|---|
| Set the VAT rate or an exemption on a line | Tax presets |
| Invoice as a small business without VAT | Small businesses |
| Know which format Quittio picks for a Belgian customer | Format choice |
| Send an invoice through Peppol | Format choice, Peppol endpoints |
| Choose the language of the invoice | Languages and language regions |
Look up a validation code such as BE-VAT-04 or BE-LANG-01 |
Rules |
| Print a QR code for the bank transfer | Payment QR code |
| Send a reminder with default interest | Late payment |
| Cancel or correct an invoice | Cancellations and corrections |
| Change the wording of a mention or rule message | Translations |
| Copy a complete invoice | Complete example |
Tax presets#
A tax preset is a ready-made VAT treatment for an invoice line. It sets the VAT category (BT-151) and the rate, and for exempt supplies also the exemption reason code (BT-121) and text (BT-120). Use a preset instead of a bare category and rate: the line then carries the wording of the mention, and the Belgian rules recognise the case. How presets work in general is described in Building invoices: Tax presets.
The categories are the codes of EN 16931: S standard rate, Z zero rated, E exempt, AE reverse charge, K intra-community supply, G export outside the EU, O not subject to VAT (Categories).
Quittio\Country\BE\Tax\VatPreset is a string-backed enum that implements TaxPreset. Apply it per line:
use Quittio\Core\Builder\InvoiceBuilder;
use Quittio\Core\Enums\UnitCode;
use Quittio\Country\BE\Tax\VatPreset;
$builder = InvoiceBuilder::make()
->deliveryDate('2026-03-02')
// ... number, seller, buyer, payment
->line('Consultancy', netPrice: '120.00', quantity: 8, unit: UnitCode::Hour, vat: VatPreset::Standard) // 21 %
->line('Hotel night', netPrice: '110.00', quantity: 2, unit: UnitCode::Day, vat: VatPreset::Accommodation) // 12 %: supplied on 2 March 2026
->line('Renovation works', netPrice: '5000.00', vat: VatPreset::ReverseChargeDomestic) // "Verlegging van heffing – KB nr. 1, art. 20"
// an exemption without its own case: the legal reference as text
->line('Training', netPrice: '300.00', vat: VatPreset::exempt('artikel 44, § 2, 4°, Wetboek btw'));| Case | Key | Category | Rate | Code (BT-121) | Use |
|---|---|---|---|---|---|
Standard |
be.standard |
S |
21 | – | Standard rate |
Intermediate |
be.intermediate |
S |
12 | – | Intermediate rate |
Reduced |
be.reduced |
S |
6 | – | Reduced rate |
Accommodation |
be.accommodation |
S |
12 from 1 March 2026, 6 before (by supply date) | – | Accommodation in hotels and campsites |
ZeroRated |
be.zero_rated |
Z |
0 | – | Zero rate, e.g. for daily newspapers |
SmallBusiness |
be.small_business |
E |
0 | – | Small business exemption scheme (Art. 56bis VAT Code); see Small businesses |
ReverseChargeDomestic |
be.reverse_charge_domestic |
AE |
0 | VATEX-EU-AE |
Domestic reverse charge, e.g. works on immovable property (Art. 20 Royal Decree No. 1) |
ReverseChargeEu |
be.reverse_charge_eu |
AE |
0 | VATEX-EU-AE |
Service to a business in another member state, which owes the VAT (Art. 196 of the VAT Directive 2006/112/EC) |
IntraCommunitySupply |
be.intra_community_supply |
K |
0 | VATEX-EU-IC |
Exempt intra-community supply (Art. 39bis VAT Code) |
Export |
be.export |
G |
0 | VATEX-EU-G |
Exempt export (Art. 39 VAT Code) |
NotTaxableInBelgium |
be.not_taxable |
O |
none | VATEX-EU-O |
Supply not taxable in Belgium (place of supply abroad) |
MarginSecondHand |
be.margin_second_hand |
E |
0 | VATEX-EU-F |
Margin scheme, second-hand goods (Art. 58 § 4 VAT Code) |
MarginArt |
be.margin_art |
E |
0 | VATEX-EU-I |
Margin scheme, works of art (Art. 58 § 4 VAT Code) |
MarginAntiques |
be.margin_antiques |
E |
0 | VATEX-EU-J |
Margin scheme, collectors' items and antiques (Art. 58 § 4 VAT Code) |
TravelAgency |
be.travel_agency |
E |
0 | VATEX-EU-D |
Margin scheme of travel agencies (Art. 58 § 3 VAT Code) |
VatPreset::exempt(string $paragraph, $code = null) |
be.exempt |
E |
0 | $code (enum, raw code or string) |
Any other exemption, e.g. artikel 44, § 2, 1° Wetboek btw |
The codes in column BT-121 are VATEX codes, the standard codes for the reason of an exemption. The rates are those of Royal Decree No. 20 on the VAT rates; Quittio\Country\BE\Tax\TaxTables holds them (STANDARD_RATE, INTERMEDIATE_RATE, REDUCED_RATE, ACCOMMODATION_RATE).
Dated rate. Accommodation is the only preset with a dated rate. A line takes the rate that applied on its supply date: 6 % up to 28 February 2026, 12 % from 1 March 2026 (TaxTables::accommodationRateOn($date)). The supply date is determined as for the German presets (Germany: Tax presets): the end of the line period, its start, the VAT point date, the delivery date, the end of the invoicing period or the issue date. VatPreset::Accommodation->rate() returns the rate of today (time zone Europe/Brussels), VatPreset::Accommodation->on($date) the preset with the rate of a date. The other presets have one rate.
Texts. Standard, Intermediate, Reduced, Accommodation and ZeroRated carry no text, because EN 16931 forbids an exemption reason for categories S and Z (BR-S-10, BR-Z-10). The other presets print an exemption reason (BT-120) from resources/lang/{nl,fr,de,en}/tax.php:
| Key | Dutch | French |
|---|---|---|
be.small_business |
Bijzondere vrijstellingsregeling kleine ondernemingen | Régime particulier de franchise des petites entreprises |
be.reverse_charge_domestic |
Verlegging van heffing – KB nr. 1, art. 20 | Autoliquidation – A.R. n° 1, art. 20 |
be.reverse_charge_eu |
Verlegging van heffing – artikel 196 Richtlijn 2006/112/EG | Autoliquidation – article 196 de la directive 2006/112/CE |
be.intra_community_supply |
Vrijgestelde intracommunautaire levering – artikel 39bis Wetboek btw | Livraison intracommunautaire exemptée – article 39bis du Code de la TVA |
be.export |
Vrijgestelde uitvoer – artikel 39 Wetboek btw | Exportation exemptée – article 39 du Code de la TVA |
be.not_taxable |
Niet in België belastbaar (plaats van de handeling in het buitenland) | Non imposable en Belgique (lieu de l'opération à l'étranger) |
be.margin_second_hand |
Bijzondere regeling – gebruikte goederen | Régime particulier – biens d'occasion |
be.margin_art |
Bijzondere regeling – kunstvoorwerpen | Régime particulier – objets d'art |
be.margin_antiques |
Bijzondere regeling – voorwerpen voor verzamelingen of antiquiteiten | Régime particulier – objets de collection et d'antiquité |
be.travel_agency |
Bijzondere regeling – reisbureaus | Régime particulier – agences de voyages |
be.exempt |
Vrijgesteld van btw – :paragraph | Exempté de la TVA – :paragraph |
The German and English texts are in the same files (Translations).
How presets behave:
- Language. The text is taken in the invoice locale (
->locale()on the builder) when the line is built. The enum presets use Dutch when the invoice has no locale; a regional locale such asfr_BEornl_BEtakes the text of its language.VatPreset::exempt()(aQuittio\Country\BE\Tax\CustomVatPreset) behaves the same: Dutch without a locale, the text of the language for a regional locale. - Preset key. The key stays on the line (
$line->vat->presetKey). The small business detection ofBE-VAT-06and the format choice rely on it. - VAT breakdown. Category, rate, code and text flow into the VAT breakdown (BG-23), and the PDF lists the texts in its legal notes.
- Lookup by key, e.g. from a database column:
VatPreset::from('be.standard'),VatPreset::fromKey($key)(null for keys of other packs),(new CountryBE)->taxPresets()->get('be.standard')or, across all installed packs,$quittio->countries()->taxPreset('be.standard').be.exemptis not part of the collection. - Default VAT. Lines without
vat:take the default VAT. WithCountryDefaultVat(installed by the Laravel integration) that isVatPreset::Standardfor a Belgian seller, andVatPreset::SmallBusinessfor a small business invoice.
Small businesses#
A small business under the exemption scheme (Art. 56bis VAT Code) charges no VAT. Mark its invoices in one of two ways:
- Use
VatPreset::SmallBusinesson every line. Its text "Bijzondere vrijstellingsregeling kleine ondernemingen" / "Régime particulier de franchise des petites entreprises" appears in the VAT breakdown and the PDF. - Or flag the invoice with
->flag(Meta::FLAG_SMALL_BUSINESS)(in Laravelquittio.seller.small_business = true). The flag does not set texts; lines withoutvat:then getVatPreset::SmallBusinessthrough the default VAT.
Either way, BE-VAT-06 reports every VAT row with a VAT amount as an error. A small business invoice to a Belgian business is still sent through Peppol (Format choice).
Rules#
The pack checks the mentions of Art. 5 of Royal Decree No. 1 and Art. 53 of the VAT Code, and the use of languages. These rules apply to every invoice of a Belgian seller, independent of the format. They run at validation level L3 (Validation levels); the class is Quittio\Country\BE\Rules\VatCodeRules.
| Code | Severity | Check |
|---|---|---|
BE-VAT-01 |
error | The seller's VAT number (BT-31), or that of its tax representative, is stated (Art. 5 § 1, 3° Royal Decree No. 1). |
BE-VAT-02 |
error | The addresses of seller and buyer are complete: street (BT-35, BT-50), post code (BT-38, BT-53) and city (BT-37, BT-52) (Art. 5 § 1, 5° Royal Decree No. 1). One message per missing field. |
BE-VAT-03 |
warning | An invoice to a business (not a consumer) with an address in Belgium states the buyer's VAT number (BT-48) (Art. 5 § 1, 4° Royal Decree No. 1). The mention is required if the buyer files VAT returns, which Quittio cannot know, hence a warning. |
BE-VAT-04 |
error | Every VAT row of category AE (reverse charge) has a text (BT-120) that contains a reverse charge statement: "verlegging", "autoliquidation", "Steuerschuld", "reverse charge" or the text of a reverse charge preset in any of the pack's languages (Art. 5 § 1, 10° Royal Decree No. 1). |
BE-VAT-05 |
error | Every VAT row of category E, K or G has the reference to the provision as text (BT-120); a VATEX code alone is not enough (Art. 5 § 1, 10° Royal Decree No. 1). |
BE-VAT-06 |
error | A small business invoice (flag Meta::FLAG_SMALL_BUSINESS or a line with VatPreset::SmallBusiness) has no VAT row with a VAT amount (Art. 56bis VAT Code). |
BE-VAT-07 |
error, warning in XML | A self-billed invoice (self-billed type code) has a note (BT-22) with "self-billing", "autofacturation", "zelffacturering", "Gutschrift", "factuur uitgereikt door de afnemer" or "facture émise par le preneur" (Art. 5 § 1, 12° Royal Decree No. 1). When the invoice is validated for an XML format, the type code labels it already, so the missing words are a warning; for a document without XML they are an error. |
BE-VAT-08 |
warning | The invoice is issued by the 15th day of the month following the supply (Art. 53 § 2 VAT Code). The supply date is the latest of the delivery date (BT-72), the end of the invoicing period (BG-14) and the end of each line period; without any of them the rule does not check. Not checked for consumers, credit notes, corrected invoices and prepayment invoices. A late invoice stays valid, hence a warning. |
BE-LANG-01 |
warning | A business in the Dutch, French or German language region invoices customers in Belgium in the language of its region (language decrees of the Flemish and French Communities; for the German language region Art. 52 of the laws on the use of languages in administrative matters of 18 July 1966). Brussels-Capital is not checked. See Languages and language regions. |
The messages and hints exist in Dutch, French, German and English. To switch a rule off or change its severity, e.g. BE-LANG-01 for a business that knows its language obligations, use validation.rules (Switching rules off and changing their severity).
For Peppol invoices the Peppol rules apply on top of these (Peppol rules). The one you meet most often is PEPPOL-EN16931-R003: a Peppol invoice needs a buyer reference (BT-10, ->buyerReference()) or a purchase order reference (BT-13, ->purchaseOrderReference()).
Mandatory PDF output#
After rendering, the mandatory field guard checks that the PDF shows certain values (Templates: mandatory field guard). CountryBE::mandatoryOutput() adds these Belgian values to the base fields:
| Condition | Access key | Description | Legal basis |
|---|---|---|---|
| the seller or its tax representative has a VAT number | seller.vatId|taxRepresentative.vatId |
VAT number of the seller | Art. 5 § 1, 3° KB nr. 1 |
| the buyer has a VAT number | buyer.vatId |
VAT number of the buyer | Art. 5 § 1, 4° KB nr. 1 |
each VAT row of category AE, K, G or E |
vatRows[i].label or the legal note with its BT-120 text |
reverse charge or exemption mention | Art. 5 § 1, 10° KB nr. 1 |
The default templates print all of them.
Format choice#
Since 1 January 2026 invoices between businesses established in Belgium are structured e-invoices, by default Peppol BIS Billing 3.0 (law of 6 February 2024). The pack's format rule, Quittio\Country\BE\Formats\BelgianFormatRule, chooses the format peppol (profile peppol) when:
- the buyer is not a consumer,
- seller and buyer both have an address in Belgium,
- and the invoice does not consist only of exempt lines (category
E) other thanVatPreset::SmallBusiness.
The last point leaves out businesses that only perform exempt supplies (Art. 44 VAT Code). The pack decides it per invoice from the lines: an invoice whose lines are all of category E, for example with VatPreset::exempt() or a margin scheme preset, is not forced to Peppol.
Every other invoice keeps the automatic choice of the core (Automatic choice): a consumer gets a PDF, a business in another EU country ZUGFeRD, unless a Peppol lookup finds it in the Peppol network. A format you request yourself (->format('pdf'), Requesting a format) always wins over the rule.
The format peppol produces UBL XML only (Peppol BIS Billing 3.0). Render a PDF next to it with ->formats(['peppol', 'pdf']); the PDF carries a note that it is a readable copy of the e-invoice and that only the XML is binding. Sending the XML through a Peppol access point is not part of this pack.
Peppol endpoints#
Peppol addresses seller and buyer by their electronic address (BT-34, BT-49). Belgian businesses are identified by their enterprise number (ondernemingsnummer / numéro d'entreprise) with the scheme 0208. The pack completes the addresses before calculation and validation (CountryBE::complete()): a seller or buyer without electronic address and with a Belgian VAT number gets the enterprise number of that VAT number as endpoint. BE0712345630 becomes <cbc:EndpointID schemeID="0208">0712345630</cbc:EndpointID>. An electronic address you set yourself is kept:
->buyer(Party::make('Voorbeeld Handel NV')
->vatId('BE0765432146')
->address('Korenmarkt 10', '9000', 'Gent', 'BE')
->electronicAddress('0765432146', '0208')) // or another scheme Peppol acceptsThe setting peppol_endpoints = false turns the completion off (Settings). A Peppol invoice without electronic addresses then fails the Peppol rules.
Quittio\Country\BE\Support\EnterpriseNumber checks and formats enterprise numbers: ten digits, the first one 0 or 1, the last two 97 minus the first eight modulo 97.
| Method | Result |
|---|---|
EnterpriseNumber::normalize('BE 0712 345 630') |
'0712345630': the ten digits, a leading 0 added to nine digits; null for other lengths |
EnterpriseNumber::isValid('0712.345.630') |
true when the check digits are right |
EnterpriseNumber::fromVatId('BE0712345630') |
'0712345630'; null for a VAT number of another country or with wrong check digits |
EnterpriseNumber::format('712345630') |
'0712.345.630', the usual notation |
EnterpriseNumber::SCHEME |
'0208' |
Languages and language regions#
The pack's default locale is nl_BE. It applies when neither the render call, the invoice (->locale()) nor the configuration (quittio.defaults.locale) sets one (Translations and locales). The core has labels in Dutch, French, German and English, so nl_BE, fr_BE and de_BE print in the language with Belgian number and date formats.
Belgian law ties the language of an invoice to the language region of the business's place of operation: the decrees on the use of languages of the Flemish Community (decree of 19 July 1973) and of the French Community (decree of 30 June 1982) require documents that the law prescribes, such as invoices, in the language of the region. BE-LANG-01 warns when a seller in a language region invoices a customer in Belgium in another language. Set the locale that matches the seller:
| Seller in | Post codes | Locale | BE-LANG-01 |
|---|---|---|---|
| Dutch language region (Flanders) | 1500–3999, 8000–9999 | nl_BE |
warns for other languages |
| French language region (Wallonia without the German-speaking municipalities) | 1300–1499, 4000–7999 except 4700–4791 | fr_BE |
warns for other languages |
| German language region (Eupen, Kelmis, Lontzen, Raeren, Bütgenbach, Büllingen, Amel, St. Vith, Burg-Reuland) | 4700–4791 | de_BE |
warns for other languages |
| Bilingual region Brussels-Capital | 1000–1299 | nl_BE or fr_BE |
not checked |
- The rule checks only invoices whose seller and buyer both have an address in Belgium, consumers included.
- It compares the language part of the locale (
froffr_BE) with the region. The locale is the invoice's, otherwise the locale of the run, so an invoice without locale counts as Dutch, the pack's default. A business in Wallonia should therefore always set->locale('fr_BE'). Quittio\Country\BE\Support\LanguageRegion::of($postcode)returns the region of a post code:LanguageRegion::DUTCH(nl),FRENCH(fr),GERMAN(de),BILINGUAL(bilingual) or null for a post code that is not Belgian. The pack derives the region from the post code alone.- The pack does not choose the locale from the seller's region by itself; the warning points out a mismatch instead (Known limitations).
Payment QR code#
The pack sets the payment QR code to girocode (CountryDefaults::$paymentQr) and ships no generator of its own. With the default QR mode auto, the PDF prints the EPC GiroCode, the QR code of the European Payments Council for SEPA credit transfers, when the currency is EUR, the payment means is a credit transfer with a valid IBAN and an amount is due (Payment QR codes).
The structured communication of Belgian banks (OGM/VCS, "overschrijving met gestructureerde mededeling" / "virement avec communication structurée", in the form +++090/9337/55493+++) can be passed as remittance information (BT-83):
->payment(PaymentMeansCode::SepaCreditTransfer, iban: 'BE68539007547034', remittanceInformation: '+++090/9337/55493+++')The pack does not check its check digits (modulo 97), and the GiroCode carries it as unstructured remittance text, not in the field for a structured reference that the Febelfin guidelines for QR codes on invoices ask for (Known limitations).
Late payment#
When a customer pays late, the law on late payment of 2 August 2002 gives the seller interest and, from business debtors, a flat rate of 40 EUR. The pack provides these values and the reference rates. Quittio's documents do not apply them by themselves: calculate the interest with Quittio\Core\Dunning\DefaultInterest and pass the results to a payment reminder (Documents: payment reminders).
CountryBE::dunningLaw() (also CountryBE::belgianDunningLaw()) returns:
| Value | Belgium |
|---|---|
| Interest for business debtors | reference rate plus 8 percentage points |
| Interest for consumers | fixed rate of 4.50 % (b2cFixedRate), the legal interest rate of 2026 |
| Flat rate for business debtors | 40.00 EUR |
| Day count | 365 days |
| Consumer default | not set (consumerDefaultAfterDays null) |
| Reference rate | "Referentie-interestvoet (wet van 2 augustus 2002)" |
The law adds 8 points to the main refinancing rate of the European Central Bank on the first day of the half-year and rounds the sum up to the next half point; the FPS Finance (Federal Public Service Finance) publishes the result. Quittio\Country\BE\Dunning\ReferenceRateTable therefore holds the published rates minus 8 points, so that the sum is the published rate: 2.50 for 2026, which gives 10.50 %. The table starts with 1 January 2023 and ends with the second half of 2026.
use Brick\Money\Money;
use Quittio\Core\Builder\Party;
use Quittio\Core\Documents\DocumentBuilder;
use Quittio\Core\Dunning\DefaultInterest;
use Quittio\Core\Quittio;
use Quittio\Core\Support\Date;
use Quittio\Country\BE\CountryBE;
$quittio = Quittio::create(['country' => 'BE'], [new CountryBE]);
$interest = DefaultInterest::forCountry(new CountryBE); // reference rates and law of the pack
$reminder = DocumentBuilder::paymentReminder(1) // first reminder, "1. aanmaning"
->number('H-2026-0007')
->issueDate('2026-10-20')
->locale('nl_BE')
->seller(Party::make('Voorbeeld Software BV')->vatId('BE0712345630')->address('Markt 1', '8000', 'Brugge', 'BE'))
->buyer(Party::make('Voorbeeld Handel NV')->vatId('BE0765432146')->address('Korenmarkt 10', '9000', 'Gent', 'BE'))
->creditTransfer('BE68539007547034')
->openItem('F-2026-0042', '2026-08-03', '1210.00', dueDate: '2026-09-02')
// first day with interest: the day after the due date; 2026-10-20 is the first day without interest
->interestCharge(...$interest->calculate(Money::of('1210.00', 'EUR'), Date::parse('2026-09-03'), Date::parse('2026-10-20')))
->flatRate('F-2026-0042') // 40.00 EUR for this claim
->deadline('2026-11-03');
$result = $quittio->document($reminder->build())->render();
$result->save(__DIR__.'/'.$result->filename()); // 1._aanmaning_H-2026-0007.pdf- Interest. From 3 September to 19 October 2026 the rate is 10.50 %; on 1,210.00 EUR this gives 16.36 EUR for 47 days. A period that spans a change of the reference rate is split into one charge per rate. Pass
consumer: truefor a consumer: one charge at 4.50 %, whatever the dates. - Flat rate.
flatRate($claim)adds the 40.00 EUR once per claim; the builder rejects it for a consumer and twice for the same claim in one reminder (Payment reminders).$interest->flatRate()returns the amount. - Later rates. For a day after 31 December 2026,
calculate()throws anInvalidArgumentException("No Belgian reference rate is known …"). Add the rate of a new half-year, the published rate minus 8 points, withwithRate():
use Quittio\Core\Dunning\DefaultInterest;
use Quittio\Country\BE\CountryBE;
use Quittio\Country\BE\Dunning\ReferenceRateTable;
$rates = (new ReferenceRateTable)->withRate('2027-01-01', '2.50'); // example value: the published rate minus 8 points
$interest = new DefaultInterest($rates, CountryBE::belgianDunningLaw());- Consumers. The legal interest rate changes every year, but the pack has one fixed rate of 4.50 %, which it applies to any period. For another year, pass your own
DunningLawwith the rate of that year asb2cFixedRatetonew DefaultInterest(...). The rules of Book XIX of the Code of Economic Law on consumer debts are not modelled (Known limitations). - Descriptions.
DefaultInterest::forCountry()returns charges without a description; the reminder prints the default label of the core, e.g. "Nalatigheidsinterest 10,5 % per jaar op 1.210,00 € van 03/09/2026 tot 19/10/2026".
Settings#
The pack has one setting, under quittio.countries.be (Settings of a pack):
| Key | Type | Default | Effect |
|---|---|---|---|
countries.be.peppol_endpoints |
bool | true |
Seller and buyer without electronic address get the enterprise number of their Belgian VAT number as Peppol endpoint (scheme 0208, Peppol endpoints). false = set the electronic addresses yourself. |
The pack has no configuration file to publish. In Laravel, set the key in config/quittio.php:
'countries' => [
'be' => ['peppol_endpoints' => false],
],Without Laravel, pass the same array to Quittio::create(): Quittio::create(['country' => 'BE', 'countries' => ['be' => ['peppol_endpoints' => false]]], [new CountryBE]).
The pack defines no meta flags of its own. It reads the core flag Meta::FLAG_SMALL_BUSINESS (Small businesses).
Defaults#
CountryBE::defaults():
| Value | Belgium | Effect |
|---|---|---|
locale |
nl_BE |
last fallback for the render and validation locale (Languages and language regions) |
currency |
EUR |
shown by quittio:countries; not applied to invoices |
timezone |
Europe/Brussels |
informational; also the time zone of VatPreset::Accommodation->rate() |
paper |
A4 |
Paper size |
letterLayout |
free |
the pack has no letter layouts of its own (Letter layouts) |
profile |
peppol |
shown by quittio:countries; the format rule chooses Peppol where it applies (Format choice) |
paymentQr |
girocode |
Payment QR code |
vatPreset, smallBusinessVatPreset |
be.standard, be.small_business |
Default VAT |
Numbering#
CountryBE::numberingRules() is used by Managed numbering (Managed: numbering). An invoice needs a sequential number in one or more series (Art. 5 § 1, 2° Royal Decree No. 1).
| Property | Belgium |
|---|---|
defaultPattern |
F-{Y}-{seq:4}, e.g. F-2026-0042 |
defaultReset |
yearly |
allowedResets |
never, yearly (no monthly reset) |
gapless, numberConsumedOnRejection |
true, true (declared, see NumberingRules) |
Retention#
CountryBE::retention() returns new RetentionRule(7, fromEndOfCalendarYear: true): invoices are kept for seven years from the end of the calendar year of issue (Art. 60 § 3 VAT Code as amended by the law of 18 December 2025, for VAT due from 2023). An invoice issued on 5 October 2026 is kept until 31 December 2033. The Managed layer stores this date (Managed: retention and deletion).
Cancellations and corrections#
CountryBE::correctionPolicy() cancels with a credit note (type code 381, positive amounts) and corrects by cancelling and issuing a new invoice (CorrectionPolicy, Building invoices: cancellations and corrections). The titles:
| Kind | Dutch | French | German | English |
|---|---|---|---|---|
cancellation |
Creditnota | Note de crédit | Gutschrift | Credit note |
correction |
Verbeterde factuur | Facture rectificative | Berichtigte Rechnung | Corrected invoice |
self_billed |
Zelffacturering | Autofacturation | Gutschrift (Selbstfakturierung) | Self-billed invoice |
A self-billed invoice still needs the words in a note for BE-VAT-07, e.g. ->note('Zelffacturering').
Translations#
The pack ships catalogs in Dutch, French, German and English in resources/lang/{nl,fr,de,en}/:
rules.php: message and hint of every rule (BE-VAT-01toBE-VAT-08,BE-LANG-01);tax.php: the exemption reason texts of the presets, includingbe.exemptforVatPreset::exempt()(placeholder:paragraph).
They are registered as the namespace quittio-be, e.g. quittio-be::rules.BE-VAT-08.message. CountryBE::registerTranslations() registers them lazily, so presets work without a Quittio instance. A regional locale such as fr_BE takes the texts of its language; other locales fall back to English. CountryBE::locales() lists the shipped locales (de, en, fr, nl). The correction policy titles are defined in PHP and cannot be changed through files.
With Laravel, Laravel's translator is used, so the usual override files apply: lang/vendor/quittio-be/{locale}/rules.php and tax.php, also for additional locales. A file needs only the keys it changes.
Without Laravel, add a directory of your own after the pack has registered its texts; a path added later overrides earlier ones:
use Quittio\Core\I18n\Translations;
use Quittio\Core\Quittio;
use Quittio\Country\BE\CountryBE;
$quittio = Quittio::create(['country' => 'BE'], [new CountryBE]);
CountryBE::registerTranslations();
// lang/nl/rules.php: return ['BE-VAT-03' => ['hint' => 'Vraag het btw-nummer van de klant op.']];
Translations::translator()->addPath(__DIR__.'/lang', CountryBE::TRANSLATION_NAMESPACE);Change the wording of a legal mention in tax.php only after checking it; the rules BE-VAT-04 and BE-VAT-05 read the texts.
The Belgian pack's hooks#
For pack authors, this is how CountryBE fills the CountryPack contract:
| Hook | Belgium |
|---|---|
defaults() |
Defaults |
profiles() |
none (the core profile peppol is used) |
taxPresets() |
every case of VatPreset |
rules() |
BE-VAT-01 to BE-VAT-08 and BE-LANG-01 |
buyerCountryRules() |
none |
formatRules() |
BelgianFormatRule |
mandatoryOutput() |
mandatory PDF output |
legalTexts() |
none |
numberingRules() |
pattern F-{Y}-{seq:4}, resets never and yearly |
retention() |
7 years from the end of the calendar year |
dunningLaw() |
law of 2 August 2002 |
correctionPolicy() |
credit note, cancel and reissue |
letterLayouts(), paymentQr(), addressFormatter() |
defaults: free, EPC GiroCode, international address format |
translationPath() |
resources/lang of the package |
complete() (CompletesInvoices) |
Peppol endpoints |
referenceRates() (ProvidesReferenceRates) |
ReferenceRateTable |
Known limitations#
- The legal wording, rules and defaults have not been reviewed by a Belgian tax adviser (see the notice at the top).
- The locale is not chosen from the seller's language region;
BE-LANG-01points out a mismatch instead. The language rules beyond the main language are not modelled either: Flanders allows a second binding version only for customers in another EU or EEA state, the French language region an additional language of the parties' choice, Brussels Dutch or French. - Structured communication (OGM/VCS,
+++…+++, check digits modulo 97) is not checked, and the GiroCode carries it as unstructured text instead of in the reference field (EPC069-12 version 002) as the Febelfin guidelines for QR codes on invoices ask. - There is no Laravel service provider with a configuration file; set the one setting in
config/quittio.php(Settings). - Consumer debts (Book XIX of the Code of Economic Law): the free first reminder, the waiting period of 14 calendar days, interest of at most the reference rate plus 8 points and the agreed flat rates (20 EUR, 30 EUR plus 10 %, 65 EUR plus 5 %, at most 2,000 EUR) are not modelled. The consumer rate is one fixed rate of 4.50 %.
- Reference rates from 2027:
ReferenceRateTableends with the second half of 2026; add later half-years withwithRate(). - Sending through a Peppol access point is not part of the pack.
- The pack has no rules for foreign sellers invoicing Belgian buyers, and no exporters or readers.
Complete example#
A software company in Bruges invoices a trading company in Ghent for ten hours of web development and a handbook. Both are businesses in Belgium, so the pack chooses Peppol BIS, adds the enterprise numbers as Peppol endpoints and checks the Belgian mentions. The seller is in the Dutch language region, so the invoice is in Dutch.
<?php
require __DIR__.'/vendor/autoload.php';
use Quittio\Core\Builder\InvoiceBuilder;
use Quittio\Core\Builder\Party;
use Quittio\Core\Enums\PaymentMeansCode;
use Quittio\Core\Enums\UnitCode;
use Quittio\Core\Quittio;
use Quittio\Core\Tax\CountryDefaultVat;
use Quittio\Country\BE\CountryBE;
use Quittio\Country\BE\Tax\VatPreset;
$quittio = Quittio::create(['country' => 'BE'], [new CountryBE]);
InvoiceBuilder::useDefaultVat(new CountryDefaultVat($quittio->countries())); // lines without vat: 21 %
$invoice = InvoiceBuilder::make()
->number('F-2026-0042')
->issueDate('2026-10-05')
->deliveryDate('2026-09-30') // invoice by 15 October (BE-VAT-08)
->locale('nl_BE') // Bruges: Dutch language region (BE-LANG-01)
->buyerReference('PO-4711') // Peppol needs BT-10 or BT-13
->seller(Party::make('Voorbeeld Software BV')
->vatId('BE0712345630') // enterprise number 0712.345.630
->address('Markt 1', '8000', 'Brugge', 'BE')
->contact('An Peeters', '+32 50 12 34 56', '[email protected]'))
->buyer(Party::make('Voorbeeld Handel NV')
->vatId('BE0765432146')
->address('Korenmarkt 10', '9000', 'Gent', 'BE'))
->line('Webontwikkeling', netPrice: '95.00', quantity: 10, unit: UnitCode::Hour) // 21 %
->line('Handboek', netPrice: '40.00', vat: VatPreset::Reduced) // 6 %
->payment(PaymentMeansCode::SepaCreditTransfer, iban: 'BE68539007547034', remittanceInformation: '+++090/9337/55493+++')
->dueInDays(30)
->build();
// Peppol BIS (the binding e-invoice) and a readable PDF from one calculation
$result = $quittio->make($invoice)->formats(['peppol', 'pdf'])->render();
$result->format(); // "peppol"
$result->save(__DIR__.'/'.$result->filename()); // Factuur_F-2026-0042.xml
$result->as('pdf')->save(__DIR__.'/'.$result->as('pdf')->filename()); // Factuur_F-2026-0042.pdf
$result->validation()->isValid(); // true, no errors or warnings
$result->data()->seller->electronicAddress?->value; // "0712345630", scheme 0208
echo $result->data()->totals->amountDue; // EUR 1191.90render()calculates, validates and renders. An error, e.g. a missing address (BE-VAT-02) or a missing buyer reference (PEPPOL-EN16931-R003), throws aValidationException; warnings such asBE-VAT-03orBE-LANG-01are in$result->validation()->warnings(). To check without rendering, call$quittio->make($invoice)->check()(Validation).- The XML contains
<cbc:EndpointID schemeID="0208">0712345630</cbc:EndpointID>for the seller and0765432146for the buyer (Peppol endpoints). Hand it to your Peppol access point. - The PDF is in Dutch ("Factuur", "Totaal incl. btw"), with Belgian date formats and a GiroCode for the transfer.
- With Laravel, the seller and its bank account come from
config/quittio.php, andQuittio::invoice()starts the invoice (Laravel integration). Setdefaults.countrytoBEfor invoices whose seller has no country code.