CNPJ alfanumérico
O que muda e como adaptar um sistema
O que muda
Desde julho de 2026, a Receita emite CNPJs com letras. O primeiro saiu em 31 de julho, para uma agência do Banco do Brasil: 00.000.000/E08G-12.
Continuam 14 caracteres: as 12 primeiras posições aceitam letras de A a Z, e os 2 dígitos verificadores seguem só algarismos. Os CNPJs já emitidos não mudam, e os dois formatos vão conviver.
O que revisar no sistema
- Banco de dados: guarde o CNPJ como texto de 14 caracteres, como
varchar(14). Uma coluna numérica não guarda letras e perde os zeros à esquerda. - Formulários: com
type="number"ouinputmode="numeric", o teclado do celular esconde as letras. Useinputmode="text"e converta para maiúsculas o que a pessoa digitar. - Máscaras: a pontuação não muda (12.ABC.345/01DE-35), mas as 12 primeiras posições precisam aceitar letras.
- Validação: para CNPJs só com números, a conta nova dá o mesmo resultado, então uma função valida os dois formatos.
- Integrações: APIs, planilhas e arquivos que tratam o CNPJ como número quebram com letras. Compare CNPJs sem pontuação e em maiúsculas.
Expressões regulares
Sem pontuação: ^[0-9A-Z]{12}[0-9]{2}$
Com pontuação: ^[0-9A-Z]{2}\.[0-9A-Z]{3}\.[0-9A-Z]{3}/[0-9A-Z]{4}-[0-9]{2}$
Elas conferem só o formato, com as letras em maiúsculas. Os dígitos verificadores precisam de uma das funções abaixo.
Dígitos verificadores
Cada caractere vale o código ASCII menos 48 (A = 17, Z = 42); o resto é a conta de sempre, com pesos de 2 a 9 e o resto da divisão por 11. O validador mostra a conta de qualquer CNPJ.
As funções aceitam o CNPJ com ou sem pontuação, em qualquer caixa, e recusam sequências repetidas. Foram testadas com CNPJs reais e com o exemplo da Receita.
TypeScript e JavaScript
export function cnpjValido(texto: string): boolean {
const cnpj = texto.toUpperCase().replace(/[^0-9A-Z]/g, "");
if (!/^[0-9A-Z]{12}[0-9]{2}$/.test(cnpj) || /^(.)\1{13}$/.test(cnpj)) return false;
const d1 = digito(cnpj.slice(0, 12));
const d2 = digito(cnpj.slice(0, 12) + d1);
return cnpj.slice(12) === String(d1) + d2;
}
// Cada caractere vale o código ASCII menos 48; os pesos vão de 2 a 9 da direita para a esquerda.
function digito(base: string): number {
let soma = 0;
for (let i = 0; i < base.length; i++) {
soma += (base.charCodeAt(i) - 48) * (2 + ((base.length - 1 - i) % 8));
}
const resto = soma % 11;
return resto < 2 ? 0 : 11 - resto;
}
Em JavaScript, tire as anotações de tipo (: string, : boolean e : number).
Python
import re
def cnpj_valido(texto: str) -> bool:
cnpj = re.sub(r"[^0-9A-Z]", "", texto.upper())
if not re.fullmatch(r"[0-9A-Z]{12}[0-9]{2}", cnpj) or len(set(cnpj)) == 1:
return False
d1 = digito(cnpj[:12])
d2 = digito(cnpj[:12] + str(d1))
return cnpj[12:] == f"{d1}{d2}"
# Cada caractere vale o código ASCII menos 48; os pesos vão de 2 a 9 da direita para a esquerda.
def digito(base: str) -> int:
soma = sum((ord(c) - 48) * (2 + (len(base) - 1 - i) % 8) for i, c in enumerate(base))
resto = soma % 11
return 0 if resto < 2 else 11 - resto
Java
import java.util.Locale;
public final class Cnpj {
public static boolean valido(String texto) {
String cnpj = texto.toUpperCase(Locale.ROOT).replaceAll("[^0-9A-Z]", "");
if (!cnpj.matches("[0-9A-Z]{12}[0-9]{2}") || cnpj.chars().distinct().count() == 1) return false;
int d1 = digito(cnpj.substring(0, 12));
int d2 = digito(cnpj.substring(0, 12) + d1);
return cnpj.substring(12).equals("" + d1 + d2);
}
// Cada caractere vale o código ASCII menos 48; os pesos vão de 2 a 9 da direita para a esquerda.
private static int digito(String base) {
int soma = 0;
for (int i = 0; i < base.length(); i++) {
soma += (base.charAt(i) - 48) * (2 + (base.length() - 1 - i) % 8);
}
int resto = soma % 11;
return resto < 2 ? 0 : 11 - resto;
}
}
Go
package cnpj
import (
"regexp"
"strconv"
"strings"
)
var (
pontuacao = regexp.MustCompile("[^0-9A-Z]")
formato = regexp.MustCompile("^[0-9A-Z]{12}[0-9]{2}$")
)
func Valido(texto string) bool {
cnpj := pontuacao.ReplaceAllString(strings.ToUpper(texto), "")
if !formato.MatchString(cnpj) || strings.Count(cnpj, cnpj[:1]) == len(cnpj) {
return false
}
d1 := digito(cnpj[:12])
d2 := digito(cnpj[:12] + strconv.Itoa(d1))
return cnpj[12:] == strconv.Itoa(d1)+strconv.Itoa(d2)
}
// Cada caractere vale o código ASCII menos 48; os pesos vão de 2 a 9 da direita para a esquerda.
func digito(base string) int {
soma := 0
for i := 0; i < len(base); i++ {
soma += int(base[i]-48) * (2 + (len(base)-1-i)%8)
}
resto := soma % 11
if resto < 2 {
return 0
}
return 11 - resto
}
PostgreSQL
CREATE FUNCTION cnpj_valido(texto text) RETURNS boolean
LANGUAGE plpgsql IMMUTABLE AS $$
DECLARE
cnpj text := regexp_replace(upper(texto), '[^0-9A-Z]', '', 'g');
base text;
soma int;
resto int;
dv int[] := '{}';
BEGIN
IF cnpj !~ '^[0-9A-Z]{12}[0-9]{2}$' OR cnpj ~ '^(.)\1{13}$' THEN
RETURN false;
END IF;
-- Cada caractere vale o código ASCII menos 48; os pesos vão de 2 a 9 da direita para a esquerda.
FOR k IN 1..2 LOOP
base := left(cnpj, 11 + k);
soma := 0;
FOR i IN 1..length(base) LOOP
soma := soma + (ascii(substr(base, i, 1)) - 48) * (2 + (length(base) - i) % 8);
END LOOP;
resto := soma % 11;
dv := dv || CASE WHEN resto < 2 THEN 0 ELSE 11 - resto END;
END LOOP;
RETURN right(cnpj, 2) = dv[1]::text || dv[2]::text;
END
$$;
Para recusar CNPJ inválido na gravação: ALTER TABLE empresas ADD CONSTRAINT cnpj_ok CHECK (cnpj_valido(cnpj));