Exemplo
Validar CNPJ numérico e alfanumérico no cadastro
Função em JavaScript, sem dependências, que normaliza e valida o CNPJ numérico e o alfanumérico (letras nas 12 primeiras posições) pelo dígito verificador, antes de chamar a API.
validarCnpj() devolve { valido, cnpj } para os dois formatos de CNPJ, o numérico e o alfanumérico que a Receita Federal emite desde 2026, e diz o motivo quando recusa: formato, repetido ou digito. Roda no formulário, antes de qualquer chamada, e não tem dependência.
O problema
Validação de CNPJ escrita para 14 dígitos recusa o CNPJ alfanumérico, que tem letras nas 12 primeiras posições; máscara de entrada com ##.###.###/####-## também. Ao mesmo tempo, mandar para a API tudo o que o usuário digitou gasta uma chamada por erro de digitação.
O fluxo
- Normaliza: tira espaços nas pontas, passa para maiúsculas e remove pontos, barra e hífen. É a forma que a API devolve em
data.cnpj. - Confere o formato: 12 caracteres entre letras maiúsculas e dígitos, mais 2 dígitos verificadores numéricos.
- Recusa os 14 caracteres iguais, que passam no cálculo mas não são CNPJ.
- Calcula os dois dígitos: cada caractere vale o código ASCII menos 48 (dígitos de 0 a 9, letras de 17 a 42), módulo 11 com pesos de 2 a 9 da direita para a esquerda; resto menor que 2 dá 0, senão 11 menos o resto. Só com dígitos, é o cálculo de sempre.
A API valida de novo: formato ou dígito inválido responde 400 invalid_cnpj, e CNPJ válido que não existe responde 404 not_found, os dois sem custo. Mais em CNPJ alfanumérico e Dígito verificador do CNPJ.
O código
// Validação de CNPJ no cadastro, antes de chamar a API: numérico e alfanumérico.
//
// node validar-cnpj.mjs 12.ABC.345/01DE-35
//
// Desde 2026 a Receita emite CNPJ alfanumérico: as 12 primeiras posições
// aceitam letras maiúsculas e dígitos, os 2 dígitos verificadores continuam
// numéricos. Cada caractere vale (código ASCII - 48): dígitos valem 0 a 9,
// letras de 17 (A) a 42 (Z). Módulo 11 com pesos 2 a 9 da direita para a
// esquerda; resto menor que 2 dá 0, senão 11 - resto. Para CNPJ só com
// dígitos, é o cálculo de sempre.
//
// Validar aqui evita uma chamada por erro de digitação. A API valida de novo e
// responde 400 invalid_cnpj, sem custo; CNPJ válido que não existe responde
// 404 not_found.
import { pathToFileURL } from "node:url";
const PESOS = [2, 3, 4, 5, 6, 7, 8, 9];
// Maiúsculas, sem pontuação e sem espaços: a forma que a API devolve em data.cnpj.
export function normalizarCnpj(valor) {
return String(valor).trim().toUpperCase().replace(/[./-]/g, "");
}
function digito(base) {
let soma = 0;
for (let i = 0; i < base.length; i++) {
soma += (base.charCodeAt(base.length - 1 - i) - 48) * PESOS[i % 8];
}
const resto = soma % 11;
return resto < 2 ? 0 : 11 - resto;
}
// { valido, cnpj } ou { valido: false, cnpj, motivo: "formato" | "repetido" | "digito" }
export function validarCnpj(valor) {
const cnpj = normalizarCnpj(valor);
if (!/^[A-Z0-9]{12}[0-9]{2}$/.test(cnpj)) return { valido: false, cnpj, motivo: "formato" };
if (/^(.)\1{13}$/.test(cnpj)) return { valido: false, cnpj, motivo: "repetido" };
const d1 = digito(cnpj.slice(0, 12));
const d2 = digito(cnpj.slice(0, 12) + d1);
if (cnpj.slice(12) !== `${d1}${d2}`) return { valido: false, cnpj, motivo: "digito" };
return { valido: true, cnpj };
}
if (process.argv[1] && import.meta.url === pathToFileURL(process.argv[1]).href) {
const entrada = process.argv[2];
if (!entrada) {
console.error("Uso: node validar-cnpj.mjs <CNPJ>");
process.exit(2);
}
const resultado = validarCnpj(entrada);
console.log(JSON.stringify(resultado));
process.exit(resultado.valido ? 0 : 1);
}
import { validarCnpj } from "./validar-cnpj.mjs";
validarCnpj("12.ABC.345/01DE-35"); // { valido: true, cnpj: "12ABC34501DE35" }
validarCnpj("00000000000192"); // { valido: false, cnpj: "00000000000192", motivo: "digito" }
Os testes, com os vetores numérico, alfanumérico, dígito errado, repetição e formato, estão em node/validar-cnpj.test.mjs e rodam com node --test. A validação não chama a API e não consome créditos.
Atualizado em