Regex Dutch VAT number (btw-nummer)
The Dutch VAT number is 'NL', 9 digits, the letter 'B' and a 2-digit counter, i.e. 12 characters after the prefix. The Commission's VIES FAQ describes it as one line of 12 generic characters, 'NLSSSSSSSSSSSS', without detailing positions; the precise structure (9 digits, 'B', 2 digits) comes from python-stdnum and jsvat. Since 1 January 2020, a sole proprietor's number no longer contains the BSN: its check is no longer the '11-test' but a modulo 97. The four attempts read only check the shape; validator.js makes the 'NL' prefix optional, and none rejects a 'B00' counter or an all-zero block. Here's an anchored pattern that rejects them, compared against those attempts.
/^NL(?!0{9})[0-9]{9}B(?!00)[0-9]{2}$/passes 26/26 cases
Comparison of existing patterns
| Source | pattern | Score on the corpus | ReDoS |
|---|---|---|---|
| validator.js (isVAT) — préfixe « NL » facultatif (« 004495445B01 » nu passe) ; « NL004495445B00 » et un bloc de neuf zéros passent | ^(NL)?\d{9}B\d{2}$ | 23 / 26 | safe |
| jsvat (netherlands.ts) — ancrée, préfixe obligatoire ; « NL004495445B00 » et « NL000000000B01 » passent (la clé est contrôlée à part par calcFn) | ^(NL)(\d{9}B\d{2})$ | 24 / 26 | safe |
| valvat (syntax.rb, A et Z remplacés par ^ et $) — ancrée, préfixe obligatoire ; « NL004495445B00 » et « NL000000000B01 » passent | ^NL[0-9]{9}B[0-9]{2}$ | 24 / 26 | safe |
| Regular Expressions Cookbook, recette 4.21 (27 pays en une regex, insensible à la casse) — préfixe facultatif (« 004495445B01 » nu), minuscules acceptées (« nl004495445b01 »), « B00 » et bloc de zéros passent | ^((AT)?U[0-9]{8}|(BE)?0[0-9]{9}|(BG)?[0-9]{9,10}|(CY)?[0-9]{8}L|(CZ)?[0-9]{8,10}|(DE)?[0-9]{9}|(DK)?[0-9]{8}|(EE)?[0-9]{9}|(EL|GR)?[0-9]{9}|(ES)?[0-9A-Z][0-9]{7}[0-9A-Z]|(FI)?[0-9]{8}|(FR)?[0-9A-Z]{2}[0-9]{9}|(GB)?([0-9]{9}([0-9]{3})?|[A-Z]{2}[0-9]{3})|(HU)?[0-9]{8}|(IE)?[0-9]S[0-9]{5}L|(IT)?[0-9]{11}|(LT)?([0-9]{9}|[0-9]{12})|(LU)?[0-9]{8}|(LV)?[0-9]{11}|(MT)?[0-9]{8}|(NL)?[0-9]{9}B[0-9]{2}|(PL)?[0-9]{10}|(PT)?[0-9]{9}|(RO)?[0-9]{2,10}|(SE)?[0-9]{12}|(SI)?[0-9]{8}|(SK)?[0-9]{10})$ | 21 / 26 | safe |
Implementation
const re = new RegExp("^NL(?!0{9})[0-9]{9}B(?!00)[0-9]{2}$", "");
re.test(input);Limits
- Valide seulement la FORME : « NL123456789B90 » a la bonne structure mais ne vérifie ni le test « 11 » ni le modulo 97 et passe ce motif. Les deux algorithmes ne sont pas recalculés par la regex.
- La FAQ VIES de la Commission décrit seulement 12 caractères génériques (lettres, chiffres, « + » ou « * ») ; ce motif suit la structure plus stricte de python-stdnum et jsvat. Si VIES acceptait un jour d'autres caractères, ce motif les refuserait.
- Le refus du compteur « B00 » et d'un bloc de neuf zéros vient de python-stdnum (sources secondaires). Aucune vérification d'existence (VIES, Belastingdienst).
- Ne pas confondre avec le BSN (burgerservicenummer, 9 chiffres) ni avec le numéro de KvK, qui ont d'autres formats. Pas de tolérance pour espaces ou minuscules ; normalisez avant validation.