A função
const W1 = [5,4,3,2,9,8,7,6,5,4,3,2];
const W2 = [6,5,4,3,2,9,8,7,6,5,4,3,2];
const val = (c) => c.charCodeAt(0) - 48; // '0'->0 ... 'Z'->42
const dvCNPJ = (base, w) => {
let s = 0;
for (let i = 0; i < w.length; i++) s += val(base[i]) * w[i];
const r = s % 11;
return r < 2 ? 0 : 11 - r;
};
function isValidCNPJ(cnpj) {
cnpj = String(cnpj).replace(/[./-]/g, '').toUpperCase();
if (!/^[A-Z0-9]{12}\d{2}$/.test(cnpj)) return false; // 12 base + 2 DV numérico
if (/^(.)\1{13}$/.test(cnpj)) return false; // repetidos
const d1 = dvCNPJ(cnpj.slice(0, 12), W1);
const d2 = dvCNPJ(cnpj.slice(0, 12) + d1, W2);
return d1 === Number(cnpj[12]) && d2 === Number(cnpj[13]);
}
isValidCNPJ('12.ABC.345/01DE-35'); // true (alfanumérico, exemplo SERPRO)
isValidCNPJ('11.222.333/0001-81'); // true (numérico)
isValidCNPJ('11.222.333/0001-00'); // falseO mesmo código valida os dois formatos: o numérico é um caso particular do alfanumérico. A base tem 12 posições (letras A–Z ou dígitos 0–9) e os 2 verificadores são sempre numéricos.
Por que ASCII − 48
No módulo 11 cada posição vira um número antes de multiplicar pelo peso. Para tratar letra e dígito de forma uniforme, converte-se o caractere pelo seu código ASCII menos 48: '0' vira 0, '9' vira 9 e 'A' vira 17 (segue até 'Z' = 42). É exatamente o que val() faz com charCodeAt(0) - 48.
val('0'); // 0
val('9'); // 9
val('A'); // 17
val('Z'); // 42Os pesos vêm em duas listas (W1 para o 1º dígito, W2 para o 2º) e o resto da divisão por 11 define o verificador — resto < 2 ? 0 : 11 - resto. É o módulo 11 de sempre, só com a entrada normalizada.
TypeScript
const W1 = [5,4,3,2,9,8,7,6,5,4,3,2];
const W2 = [6,5,4,3,2,9,8,7,6,5,4,3,2];
const val = (c: string): number => c.charCodeAt(0) - 48;
const dvCNPJ = (base: string, w: number[]): number => {
let s = 0;
for (let i = 0; i < w.length; i++) s += val(base[i]) * w[i];
const r = s % 11;
return r < 2 ? 0 : 11 - r;
};
export function isValidCNPJ(value: string): boolean {
const cnpj = value.replace(/[./-]/g, '').toUpperCase();
if (!/^[A-Z0-9]{12}\d{2}$/.test(cnpj)) return false;
if (/^(.)\1{13}$/.test(cnpj)) return false;
const d1 = dvCNPJ(cnpj.slice(0, 12), W1);
const d2 = dvCNPJ(cnpj.slice(0, 12) + d1, W2);
return d1 === Number(cnpj[12]) && d2 === Number(cnpj[13]);
}Cuidados
- Regex só valida formato. Um padrão como
/^[A-Z0-9]{12}\d{2}$/confirma que são 12 posições alfanuméricas + 2 dígitos, mas não confere o verificador — quem faz isso é a função acima. Veja regex de CNPJ. - Sequências repetidas (
00000000000000) passariam no módulo 11, por isso a função as descarta antes com/^(.)\1{13}$/. - Normalize antes. A função força
toUpperCase()e remove./-; minúsculas e máscara são tratadas, mas guarde sempre os 14 caracteres limpos. - Válido ≠ existe. A função confirma a consistência matemática, não se o CNPJ foi emitido a uma empresa real. Para isso é outra coisa: CNPJ válido vs. CNPJ real.
- Alfanumérico entra em jul/2026 (IN RFB nº 2.229/2024), só para novos registros. O código já está pronto para os dois formatos.