Describe the bug
isVAT(value, 'ES') rejects valid Spanish VAT identification numbers whenever the control character is a digit, which is the case for the two most common Spanish company forms.
The matcher is:
ES: /^(ES)?\w\d{7}[A-Z]$/
The final [A-Z] requires the control character to be a letter. Per the Spanish tax agency (AEAT), position 9 is "Alfanumérico. Carácter de control" and is:
- a digit
0-9 for Spanish legal entities — leading letters A, B, C, D, E, F, G, H, J, U, V, which include A (sociedad anónima) and B (sociedad de responsabilidad limitada), by far the commonest forms;
- a letter
A-J for foreign entities, public bodies, local corporations and religious congregations — leading letters N, W, P, Q, R, S.
So the current regex accepts only the second group and rejects the first.
Examples
const { isVAT } = require('validator');
isVAT('ESA28015865', 'ES'); // false — Telefónica, S.A. (real, valid)
isVAT('ESB84937783', 'ES'); // false — a real sociedad limitada
isVAT('ESA82018474', 'ES'); // false — a real sociedad anónima
isVAT('ESN0032001J', 'ES'); // true — foreign entity, letter control
isVAT('ESQ2826004J', 'ES'); // true — public body, letter control
Suggested fix
Allow both control-character forms, and optionally restrict the leading character to the letters actually assigned to a legal form (I, O and T are unassigned; K, L, M, X, Y, Z belong to natural persons):
ES: /^(ES)?[A-HJNP-SUVW]\d{7}[0-9A-J]$/
I'm happy to open a PR with tests if that shape looks right to you.
Additional context
Sources: AEAT, Composición del NIF – personas jurídicas y entidades; Orden EHA/451/2008 (BOE-A-2008-3580), arts. 3–5.
Found while adding a VAT number generator to faker-js (faker-js/faker#3985), where isVAT is used as the test oracle.
Validator.js version: 13.15.35
Node.js version: v24.18.0
OS platform: macOS
Describe the bug
isVAT(value, 'ES')rejects valid Spanish VAT identification numbers whenever the control character is a digit, which is the case for the two most common Spanish company forms.The matcher is:
ES: /^(ES)?\w\d{7}[A-Z]$/The final
[A-Z]requires the control character to be a letter. Per the Spanish tax agency (AEAT), position 9 is "Alfanumérico. Carácter de control" and is:0-9for Spanish legal entities — leading lettersA,B,C,D,E,F,G,H,J,U,V, which includeA(sociedad anónima) andB(sociedad de responsabilidad limitada), by far the commonest forms;A-Jfor foreign entities, public bodies, local corporations and religious congregations — leading lettersN,W,P,Q,R,S.So the current regex accepts only the second group and rejects the first.
Examples
Suggested fix
Allow both control-character forms, and optionally restrict the leading character to the letters actually assigned to a legal form (
I,OandTare unassigned;K,L,M,X,Y,Zbelong to natural persons):ES: /^(ES)?[A-HJNP-SUVW]\d{7}[0-9A-J]$/I'm happy to open a PR with tests if that shape looks right to you.
Additional context
Sources: AEAT, Composición del NIF – personas jurídicas y entidades; Orden EHA/451/2008 (BOE-A-2008-3580), arts. 3–5.
Found while adding a VAT number generator to faker-js (faker-js/faker#3985), where
isVATis used as the test oracle.Validator.js version: 13.15.35
Node.js version: v24.18.0
OS platform: macOS