Como validar CNPJ numérico e alfanumérico em PHP
Se a sua função de validação remove tudo que não é número, ela não está preparada para o novo CNPJ. A solução abaixo aceita tanto os CNPJs numéricos já existentes quanto o formato alfanumérico e calcula os dois dígitos verificadores conforme a regra oficial.
O que a validação precisa fazer?
O CNPJ continua com 14 posições. As 12 primeiras podem conter números de 0 a 9 e letras maiúsculas de A a Z. As duas últimas posições continuam sendo dígitos verificadores numéricos.
Para validar corretamente no sistema, precisamos separar três conceitos:
- normalização: retirar apenas a máscara e padronizar letras para maiúsculas;
- formato: confirmar 12 caracteres alfanuméricos + 2 dígitos;
- dígito verificador: recalcular os dois DVs pelo Módulo 11 e comparar com o valor informado.
Por que a função antiga deixa de funcionar?
Este padrão era muito usado para remover a máscara:
$cnpj = preg_replace('/\D/', '', $cnpj);
O problema é que \D significa “qualquer caractere que não seja dígito”. Se o CNPJ possuir letras, elas serão apagadas.
O mesmo problema aparece em código que usa is_numeric(), ctype_digit(), casts para inteiro ou um campo numérico no banco.
Função PHP completa
A implementação abaixo aceita entrada com ou sem máscara, mantém letras válidas e usa a regra oficial de cálculo dos dígitos verificadores.
function normalizarCnpj(string $cnpj): string
{
return strtoupper(
str_replace(['.', '/', '-', ' '], '', trim($cnpj))
);
}
function valorParaCalculoCnpj(string $caractere): int
{
return ord($caractere) - 48;
}
function calcularDigitoCnpj(string $base, array $pesos): int
{
$soma = 0;
foreach (str_split($base) as $indice => $caractere) {
$soma += valorParaCalculoCnpj($caractere)
* $pesos[$indice];
}
$resto = $soma % 11;
return $resto === 0 || $resto === 1
? 0
: 11 - $resto;
}
function validarCnpj(string $cnpj): bool
{
$cnpj = normalizarCnpj($cnpj);
// 12 posições alfanuméricas + 2 DVs numéricos.
if (!preg_match('/^[A-Z0-9]{12}[0-9]{2}$/D', $cnpj)) {
return false;
}
// Evita sequências numéricas sem significado cadastral,
// como 00.000.000/0000-00.
if (preg_match('/^(\d)\1{13}$/', $cnpj)) {
return false;
}
$base = substr($cnpj, 0, 12);
$dvInformado = substr($cnpj, 12, 2);
$primeiroDv = calcularDigitoCnpj(
$base,
[5, 4, 3, 2, 9, 8, 7, 6, 5, 4, 3, 2]
);
$segundoDv = calcularDigitoCnpj(
$base . $primeiroDv,
[6, 5, 4, 3, 2, 9, 8, 7, 6, 5, 4, 3, 2]
);
return $dvInformado === $primeiroDv . $segundoDv;
}
Por que usar ord($caractere) - 48?
O cálculo continua usando Módulo 11, mas cada caractere das 12 primeiras posições precisa virar um valor numérico antes da multiplicação pelos pesos. A regra técnica usa o código ASCII do caractere menos 48.
'0' => ASCII 48 - 48 = 0
'9' => ASCII 57 - 48 = 9
'A' => ASCII 65 - 48 = 17
'B' => ASCII 66 - 48 = 18
'Z' => ASCII 90 - 48 = 42
Por isso a função normaliza letras para maiúsculas antes do cálculo.
Como são calculados os dois dígitos?
Para o primeiro DV, os 12 valores são multiplicados por:
5, 4, 3, 2, 9, 8, 7, 6, 5, 4, 3, 2
Depois calculamos o resto da soma por 11. Se o resto for 0 ou 1, o dígito é 0. Nos demais casos, o dígito é 11 - resto.
O segundo DV usa os 12 caracteres originais mais o primeiro DV:
6, 5, 4, 3, 2, 9, 8, 7, 6, 5, 4, 3, 2
Casos de teste
$testes = [
'00.000.000/E08G-12' => true,
'12.ABC.345/01DE-35' => true,
'04.252.011/0001-10' => true,
'12.ABC.345/01DE-36' => false,
'12.ABC.345/01D*-35' => false,
'00.000.000/0000-00' => false,
];
foreach ($testes as $cnpj => $esperado) {
$resultado = validarCnpj($cnpj);
echo $cnpj
. ' => '
. ($resultado ? 'válido' : 'inválido')
. PHP_EOL;
}
O CNPJ 00.000.000/E08G-12 foi o primeiro CNPJ alfanumérico divulgado oficialmente pela Receita Federal. O exemplo 12.ABC.345/01DE-35 aparece na documentação oficial de demonstração do cálculo.
Exemplo do cálculo oficial
Para a base 12ABC34501DE, os caracteres viram:
1, 2, 17, 18, 19, 3, 4, 5, 0, 1, 20, 21
Aplicando os pesos do primeiro dígito, a soma é 459. O resto de 459 % 11 é 8, então:
11 - 8 = 3
O primeiro DV é 3. Repetindo o processo com esse dígito anexado à base, o segundo DV é 5. O CNPJ de exemplo termina em -35.
Por que não apagar qualquer caractere inválido?
Uma normalização como preg_replace('/[^A-Z0-9]/', '', $cnpj) pode esconder uma entrada incorreta ao simplesmente remover o caractere inesperado. Na rotina de validação deste artigo, retiramos somente os separadores conhecidos da máscara e deixamos a regex rejeitar o restante.
Banco de dados e APIs
- armazene CNPJ como texto, por exemplo
CHAR(14)para o valor normalizado; - use
stringem DTOs, JSON e contratos de API; - não converta CNPJ com
intval(),Number()ouparseInt(); - revise máscaras, regex e componentes que permitem somente números;
- mantenha compatibilidade com os CNPJs numéricos existentes.
Veja também: CNPJ alfanumérico: o que muda para desenvolvedores em 2026.
Formato, DV e situação cadastral são diferentes
- regex: verifica a estrutura;
- algoritmo do DV: verifica se os dois dígitos finais correspondem à base;
- consulta cadastral: verifica se o CNPJ foi efetivamente atribuído e qual é sua situação.
Próximo artigo do cluster
Na próxima publicação vamos tratar especificamente de regex para CNPJ alfanumérico, mostrando por que validar o formato não é a mesma coisa que validar o dígito verificador.
Fontes oficiais
- Receita Federal — Cálculo do DV do CNPJ Alfanumérico
- Receita Federal / Serpro — Manual de cálculo dos dígitos verificadores
- Receita Federal — Perguntas e Respostas do CNPJ alfanumérico
- Receita Federal — Primeiro CNPJ em formato alfanumérico
Referências técnicas conferidas em 10/09/2026.