Rexact
Cheatsheet

Regex Spanish VAT number (NIF, CIF, NIE)

The Spanish VAT number is the taxpayer's NIF preceded by 'ES': 9 characters where the first, the last, or both are letters. The Commission's VIES FAQ describes it as: 'the first and last characters may be alpha or numeric, but they may not both be numeric'. In practice a company has a CIF (letter, 7 digits, a digit or letter check), an individual a DNI (8 digits and a letter) or a NIE (X, Y or Z, 7 digits, a letter). The attempts read disagree: the published validator.js version rejects CIFs with a digit check and accepts '_' in front, its development version rejects individuals, valvat and jsvat accept any leading letter (I and O included), and the Regular Expressions Cookbook's regex accepts nine digits. Here's a pattern following the Commission's rule and excluding the letters I, O and T up front, compared against those attempts.

/^ES(?:[0-9]{8}[A-Z]|[A-HJ-NP-SU-Z][0-9]{7}[0-9A-Z])$/
passes 31/31 cases

Comparison of existing patterns

SourcepatternScore on the corpusReDoS
validator.js 13.15.35 publié sur npm (lib/isVAT.js) — « wd{7}[A-Z] » : refuse tous les CIF à clé chiffre (« ESB58378431 », « ESA12345674 »), accepte « ESI1234567A » et « ES_1234567A », préfixe facultatif^(ES)?\w\d{7}[A-Z]$24 / 31safe
validator.js, branche master (src/lib/isVAT.js) — ne couvre que les sociétés (CIF) : refuse DNI, NIE et K/L/M (« ES54362315K », « ESX5253868R »), impose le type de clé par lettre initiale ; préfixe « ES » facultatif (« B64717838 » nu passe)^(ES)?(?:[ABEH]\d{8}|[NPQRSW]\d{7}[A-J]|[CDFGJUV]\d{7}[0-9A-J])$22 / 31safe
valvat (syntax.rb, A et Z remplacés par ^ et $) — ancrée, couvre CIF, DNI et NIE ; mais « ESO12345678 » et « ESI1234567A » passent : lettre initiale libre^ES([A-Z][0-9]{8}|[0-9]{8}[A-Z]|[A-Z][0-9]{7}[A-Z])$29 / 31safe
jsvat (spain.ts) — les quatre regex de l'objet rules.regex réunies par « | » ; la lettre initiale du CIF numérique est libre (« ESO12345678 » passe) ; les clés sont contrôlées à part par calcFn^(ES)([A-Z]\d{8})$|^(ES)([A-HN-SW]\d{7}[A-J])$|^(ES)([0-9YZ]\d{7}[A-Z])$|^(ES)([KLMX]\d{7}[A-Z])$30 / 31safe
Regular Expressions Cookbook, recette 4.21 (27 pays en une regex, insensible à la casse) — branche espagnole « (ES)?[0-9A-Z][0-9]{7}[0-9A-Z] » : accepte 9 chiffres (« ES123456789 »), les minuscules et un préfixe absent^((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})$26 / 31safe

Implementation

const re = new RegExp("^ES(?:[0-9]{8}[A-Z]|[A-HJ-NP-SU-Z][0-9]{7}[0-9A-Z])$", "");
re.test(input);

Limits

  • Valide seulement la FORME : « ESB64717839 » et « ES12345678A » ont la bonne structure mais une clé fausse (lettre DNI modulo 23, clé CIF de type Luhn) et passent ce motif. La clé n'est pas recalculée.
  • Le motif accepte une clé lettre ou chiffre pour toute forme juridique, car python-stdnum signale des informations contradictoires selon la forme ; validator.js (version de développement) impose au contraire un type de clé par lettre initiale.
  • Les lettres initiales exclues (I, O, T) viennent de python-stdnum, pas de la FAQ de la Commission. Les formes K, L, M (personnes sans NIE) et X, Y, Z (NIE) sont acceptées : voir dni-nie-espagnol pour les identifiants de personnes physiques.
  • Aucune vérification d'existence (VIES). Pas de tolérance pour espaces, tirets ou minuscules : normalisez avant validation. Les numéros du corpus sont des exemples de documentation ou synthétiques.

Related pages