A implementação abaixo é ES2015+ e funciona igual no navegador e no Node.js. Não usa nenhuma biblioteca externa, então dá para colar direto em um utilitário do seu projeto ou em uma função serverless.
0=0 … 9=9, A=17 … Z=42), os pesos vão de 2 a 9 da direita para a esquerda, soma-se tudo, divide-se por 11 e o dígito é 0 quando o resto é menor que 2, ou 11 menos o resto. Ver a conta detalhada.Código completo
const NAO_ALFANUMERICO = /[^0-9A-Z]/g;
const FORMATO = /^[0-9A-Z]{12}\d{2}$/;
const REPETIDO = /^(.)\1{13}$/;
/** Remove máscara e normaliza para maiúsculas. */
export function limparCnpj(valor) {
return String(valor ?? '').toUpperCase().replace(NAO_ALFANUMERICO, '');
}
/** Calcula um dígito do módulo 11 (valor do caractere = ASCII - 48). */
function digito(sequencia) {
let soma = 0;
for (let i = 0; i < sequencia.length; i += 1) {
const valor = sequencia.charCodeAt(i) - 48;
const peso = ((sequencia.length - 1 - i) % 8) + 2;
soma += valor * peso;
}
const resto = soma % 11;
return resto < 2 ? 0 : 11 - resto;
}
/** Devolve os dois dígitos verificadores da base de 12 caracteres. */
export function calcularDv(base) {
const primeiro = digito(base);
const segundo = digito(`${base}${primeiro}`);
return `${primeiro}${segundo}`;
}
/** Valida CNPJ numérico ou alfanumérico, com ou sem máscara. */
export function validarCnpj(valor) {
const cnpj = limparCnpj(valor);
if (!FORMATO.test(cnpj)) return false;
if (REPETIDO.test(cnpj)) return false;
return calcularDv(cnpj.slice(0, 12)) === cnpj.slice(12);
}
/** Aplica a máscara 00.000.000/0000-00. */
export function formatarCnpj(valor) {
const cnpj = limparCnpj(valor);
if (cnpj.length !== 14) return cnpj;
return cnpj.replace(/^(.{2})(.{3})(.{3})(.{4})(.{2})$/, '$1.$2.$3/$4-$5');
}
Testes e integração com o framework
import test from 'node:test';
import assert from 'node:assert/strict';
import { validarCnpj, calcularDv, formatarCnpj } from './cnpj.js';
test('exemplo oficial da Receita Federal', () => {
assert.equal(calcularDv('12ABC34501DE'), '35');
assert.ok(validarCnpj('12.ABC.345/01DE-35'));
assert.ok(validarCnpj('12abc34501de35'));
});
test('continua aceitando CNPJ numérico', () => {
assert.ok(validarCnpj('11.222.333/0001-81'));
assert.equal(validarCnpj('11.222.333/0001-82'), false);
});
test('formatação', () => {
assert.equal(formatarCnpj('12ABC34501DE35'), '12.ABC.345/01DE-35');
});
Detalhes que costumam quebrar em JavaScript
charCodeAtjá devolve o código ASCII/UTF-16 do caractere — para0–9eA–Zos dois coincidem, então subtrair 48 basta.- Normalize para maiúsculas antes de calcular:
aminúsculo tem código 97, e o cálculo daria errado silenciosamente. - Se você usa TypeScript, veja a versão tipada.
Casos de teste recomendados
Cubra pelo menos estes cenários — eles pegam praticamente todos os erros de implementação:
| Entrada | Esperado | O que testa |
|---|---|---|
12.ABC.345/01DE-35 | válido | exemplo oficial, com máscara |
12abc34501de35 | válido | normalização para maiúsculas |
11.222.333/0001-81 | válido | compatibilidade com o formato numérico |
11.222.333/0001-82 | inválido | dígito verificador errado |
12ABC34501DEAB | inválido | letra no dígito verificador |
12ABC34501DE3 | inválido | tamanho incorreto |
00.000.000/0000-00 | inválido | sequência repetida |
"" / null | inválido | entrada vazia sem exceção |
Precisa de mais massa de teste? O gerador produz até 500 CNPJs alfanuméricos válidos de uma vez, com opção de baixar em .txt.