CodePráticov3.5.0
PHPCNPJ 2026

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.
Importante: validar formato e DV não confirma que a inscrição existe ou está ativa na Receita Federal. Isso é validação cadastral e depende de uma consulta a uma fonte oficial.

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 string em DTOs, JSON e contratos de API;
  • não converta CNPJ com intval(), Number() ou parseInt();
  • 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.
Conclusão: a função deste artigo valida formato + dígito verificador. Ela não substitui uma consulta cadastral oficial.

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

Referências técnicas conferidas em 10/09/2026.