Regex de CNPJ (numérico e alfanumérico) e por que a regex não basta
Para CNPJ sem máscara, aceitando os dois formatos, use ^[0-9A-Z]{12}[0-9]{2}$: 12 posições com letras maiúsculas ou dígitos e 2 dígitos verificadores numéricos. Com máscara, ^[0-9A-Z]{2}\.[0-9A-Z]{3}\.[0-9A-Z]{3}/[0-9A-Z]{4}-[0-9]{2}$. A regex só confere o formato: ela aceita qualquer dígito verificador errado e sequências repetidas, então o cálculo do módulo 11 continua sendo obrigatório.
A regex de CNPJ cabe numa linha, e é por isso que ela engana. Abaixo estão as expressões para o formato numérico e para o alfanumérico, testadas com números gerados pela biblioteca do site, e a lista do que elas não pegam. Para validar de fato, use o validador de CNPJ, que confere o formato e o dígito.
As expressões
| Para quê | Regex |
|---|---|
| Só numérico, sem máscara | ^\d{14}$ |
| Numérico e alfanumérico, sem máscara | ^[0-9A-Z]{12}[0-9]{2}$ |
| Numérico e alfanumérico, com máscara | ^[0-9A-Z]{2}\.[0-9A-Z]{3}\.[0-9A-Z]{3}/[0-9A-Z]{4}-[0-9]{2}$ |
| Com ou sem máscara (completa) | ^(?:[0-9A-Z]{12}[0-9]{2}|[0-9A-Z]{2}\.[0-9A-Z]{3}\.[0-9A-Z]{3}/[0-9A-Z]{4}-[0-9]{2})$ |
A barra vertical na última linha está escapada só por causa da tabela; na expressão, é a alternância normal.
O desenho vem do formato oficial. Segundo a Receita Federal, as 12 primeiras posições (raiz e ordem) podem ter números de 0 a 9 e letras maiúsculas de A a Z, e o dígito verificador (as 2 últimas) é numérico. A máscara é a mesma do CNPJ de sempre. Como a regra é a mesma para os dois formatos, [0-9A-Z]{12}[0-9]{2} já cobre o numérico.
O que testamos
Rodamos as expressões em JavaScript contra números gerados pela biblioteca do site:
| Teste | Resultado |
|---|---|
^\d{14}$ em 1.000 CNPJs alfanuméricos |
aceitou 0 |
^[0-9A-Z]{12}[0-9]{2}$ em 1.000 alfanuméricos |
aceitou 1.000 |
| a mesma regex em 1.000 numéricos | aceitou 1.000 |
| regex com máscara em 1.000 alfanuméricos mascarados | aceitou 1.000 |
Ou seja: a regex antiga, a que todo cadastro tem, rejeita todo CNPJ com letra. Se o seu formulário usa \d{14} ou \d{2}\.\d{3}\.\d{3}/\d{4}-\d{2}, ele recusa o novo formato antes de qualquer outra regra. O passo a passo no banco e na API está em como migrar o banco de dados.
Por que a regex não basta
Os mesmos testes mostram o outro lado:
- Dígito errado passa. Alteramos o último dígito de 1.000 CNPJs válidos. A regex aceitou os 1.000. O validador recusou os 1.000.
- Quase tudo que tem 14 dígitos passa. Em 100.000 sequências de 14 dígitos sorteadas, a regex aceitou as 100.000. O cálculo do dígito aceitou 996 (semente 2026), cerca de 1 em 100, que é a chance de dois dígitos ao acaso baterem.
- Sequência repetida passa.
00000000000000e11111111111111têm o formato certo. Nenhum validador deve aceitá-los.
A conta do dígito é módulo 11, com cada caractere convertido pelo código ASCII menos 48. Regex não faz conta. O formato é a primeira barreira, o dígito é a segunda.
Armadilhas por linguagem
JavaScript. \d só casa os dígitos de 0 a 9. Testamos: /\d/.test('٣') dá false. E, sem a flag m, $ só casa no fim do texto: ^[0-9A-Z]{12}[0-9]{2}$ recusa 12ABC34501DE35\n.
Python. Em padrão de texto (str), \d casa dígitos de outros alfabetos além de 0 a 9, e $ casa também antes de uma quebra de linha no fim. A documentação do módulo re descreve os dois comportamentos. Use [0-9] e re.fullmatch:
import re
CNPJ = re.compile(r"[0-9A-Z]{12}[0-9]{2}")
CNPJ.fullmatch("12ABC34501DE35") # casa
CNPJ.fullmatch("12ABC34501DE35\n") # None
Esse trecho em Python não foi executado em nossa máquina de testes; ele segue a documentação do re. Em outras linguagens, confira como $ trata a quebra de linha final antes de confiar na expressão.
Máscaras pela metade
É tentador fazer cada separador opcional (\.?, /?, -?). O custo é aceitar 12.ABC345/01DE-35 e 12ABC.345/01DE35, que ninguém digita de propósito e que mostram um campo mal formatado. Nos testes, a regex com alternância recusou essas duas formas e aceitou a máscara completa e a forma limpa. A alternativa mais simples é tirar a máscara antes e aplicar só a regex sem máscara.
Onde a regex ajuda
A regex tem lugar certo: o campo de tela e a entrada da API. Ela recusa de imediato o que está claramente fora do formato (13 caracteres, símbolo, letra no dígito verificador) e dá uma mensagem útil antes de qualquer cálculo. Depois dela vem o dígito, e só então a consulta ao banco. Nessa ordem, você economiza processamento e mostra ao usuário o erro certo.
Regex mais DV, em código
A combinação que funciona: normalizar, conferir o formato e recusar repetidos com a regex, depois recalcular o dígito.
const SEM_REPETIDO = /^(?!([0-9A-Z])\1{13}$)[0-9A-Z]{12}[0-9]{2}$/;
function dvCnpj(base) {
const calc = (v) => {
let soma = 0, peso = 2;
for (let i = v.length - 1; i >= 0; i--) {
soma += v[i] * peso;
peso = peso === 9 ? 2 : peso + 1;
}
const resto = soma % 11;
return resto < 2 ? 0 : 11 - resto;
};
const v = [...base].map((c) => c.charCodeAt(0) - 48); // ASCII - 48
const d1 = calc(v);
return `${d1}${calc([...v, d1])}`;
}
function cnpjValido(entrada) {
const s = String(entrada).replace(/[.\/\-\s]/g, '').toUpperCase();
return SEM_REPETIDO.test(s) && dvCnpj(s.slice(0, 12)) === s.slice(12);
}
cnpjValido('12.ABC.345/01DE-35'); // true (exemplo oficial da Receita)
cnpjValido('12abc34501de35'); // true (maiúsculas após normalizar)
cnpjValido('12ABC34501DE00'); // false (formato certo, dígito errado)
A parte (?!([0-9A-Z])\1{13}$) é uma checagem negativa: recusa o texto se os 14 caracteres forem o mesmo. Executamos o código acima, e as três chamadas devolveram os valores dos comentários.
O que nenhuma validação diz
Passar na regex e no dígito mostra que o número está bem formado. Não mostra que a empresa existe. Isso só uma consulta ao cadastro da Receita confirma, e em teste de software ela não é necessária.
Outro cuidado: a Receita informa que vai bloquear internamente algumas combinações de letras, para evitar palavras ofensivas ou confusão com números, e orienta que os sistemas aceitem todas as letras do alfabeto. Não tente reproduzir essa lista na regex.
Para testar a sua regex com números prontos, gere uma lista com os dois formatos no gerador de CNPJ, com casos inválidos incluídos. Para conferir um número e ver o cálculo do dígito passo a passo, use o validador de CNPJ. Os números gerados são fictícios e valem apenas para teste de software.
Perguntas frequentes
Qual a regex do CNPJ alfanumérico?
Sem máscara: ^[0-9A-Z]{12}[0-9]{2}$. As 12 primeiras posições aceitam letras de A a Z e números, e as 2 últimas só números, porque são os dígitos verificadores.
A regex antiga de CNPJ, com \d, ainda funciona?
Só para os CNPJs numéricos. Em 1.000 CNPJs alfanuméricos gerados para teste, a expressão ^\d{14}$ aceitou zero. Troque \d por [0-9A-Z] nas 12 primeiras posições.
A regex valida o dígito verificador do CNPJ?
Não. Em 1.000 CNPJs com o dígito alterado de propósito, a regex aceitou os 1.000. O dígito exige o cálculo do módulo 11, que uma expressão regular não faz.
Como aceitar CNPJ com e sem máscara na mesma regex?
Use uma alternância entre a forma sem máscara e a forma completa com máscara. Evite pontos e barra opcionais um a um, porque eles aceitam máscaras pela metade como 12.ABC345/01DE-35.
Devo aceitar letra minúscula?
O número oficial usa só maiúsculas. Converta a entrada para maiúsculas antes de aplicar a regex, em vez de liberar minúsculas na expressão, para gravar sempre no mesmo formato.
Fontes
Leia também
NIT, NIS, PIS e PASEP: qual a diferença e qual número usar
NIT é o número de inscrição do trabalhador na Previdência Social (INSS). Quem já tem PIS, PASEP ou NIS usa esse mesmo número como NIT e não ganha o…
14/11/2026 FiscalChave de acesso da NF-e em homologação, bloco a bloco
A chave de acesso da NF-e tem 44 posições e é igual em homologação e em produção: nada nela indica o ambiente, que vai no campo tpAmb da nota. Em h…
14/11/2026Veja também
Atualizado em 14/11/2026.