Pular para o conteúdo
RuaCEP
Guias Práticos7 min de leitura

Validar CEP: regex, máscara e código pronto

Regex para CEP, máscara no campo, validação de formato e de existência e exemplos em JavaScript, PHP e Python, com consulta a uma API de CEP.

Por RuaCEPPublicado em

Validar um CEP tem duas etapas diferentes: conferir o formato (oito dígitos, com hífen opcional depois do quinto) e conferir se o código existe. A expressão regular resolve a primeira. Só uma consulta a uma base de CEPs resolve a segunda. Este guia traz a regex, a máscara do campo, a checagem pela faixa do estado e exemplos em JavaScript, PHP e Python.

Regex do formato

A forma mais citada é ^\d{5}-?\d{3}$: começo da string, cinco dígitos, hífen opcional, três dígitos e fim da string. Sem as âncoras ^ e $, a regex encontraria um “CEP” no meio de um CPF ou de um telefone.

Prefira escrever [0-9] no lugar de \d. Em JavaScript e no PHP sem o modificador u, as duas formas são equivalentes, mas no Python 3 o \d aceita também dígitos de outros sistemas de escrita, como os usados no árabe e no devanágari. Com [0-9], a mesma regex funciona igual nas três linguagens.

Não valide CEP como número. Um campo numérico apaga o zero à esquerda, e todo CEP de 01000-000 a 09999-999, do estado de São Paulo, começa com zero. Guarde e compare o CEP como texto.

Normalizar a entrada sem perder o zero

Pessoas digitam CEP com espaço, ponto ou sem hífen. Para aceitar essas variações, remova tudo o que não for dígito e confira se sobraram exatamente oito:

function normalizarCep(entrada) {
  const digitos = entrada.replace(/[^0-9]/g, '')
  return digitos.length === 8 ? digitos : null
}

normalizarCep('01.310-100') // '01310100'
normalizarCep('1310100')    // null: faltou um dígito

Como o valor continua sendo texto, o zero inicial fica. Não corte sequências maiores para caber em oito dígitos: se a pessoa colou um CPF ou um telefone, o resultado seria um CEP com formato válido e endereço errado. Para exibir, junte as partes com o hífen: os cinco primeiros dígitos, o hífen e os três últimos.

Máscara no campo com JavaScript puro

<label for="cep">CEP</label>
<input id="cep" name="cep" type="text" inputmode="numeric"
  autocomplete="postal-code" placeholder="00000-000"
  pattern="[0-9]{5}-?[0-9]{3}">
  • type="text" mantém o zero à esquerda; um campo numérico não mantém.
  • inputmode="numeric" abre o teclado numérico no celular sem transformar o valor em número.
  • autocomplete="postal-code" permite que o navegador preencha o CEP salvo da pessoa.
  • pattern faz a validação nativa do navegador; o atributo já exige que o valor inteiro corresponda, então não leva ^ nem $.
  • Sem maxlength: o navegador aplica esse limite antes do script e cortaria um CEP colado com ponto ou espaço, como 01.310-100, que viraria 01310-10. A máscara e o pattern já cuidam do tamanho.
const campo = document.querySelector('#cep')

campo.addEventListener('input', () => {
  const digitos = campo.value.replace(/[^0-9]/g, '')
  if (digitos.length > 8) return // deixe a validação avisar; não corte o que foi colado
  if (digitos.length > 5) {
    campo.value = digitos.slice(0, 5) + '-' + digitos.slice(5)
  } else {
    campo.value = digitos
  }
})

A máscara só organiza o que foi digitado. Não bloqueie colar no campo e mostre a mensagem de erro perto dele. Para limpar uma lista que já existe, o formatador de CEP faz o trabalho no navegador.

Formato válido não é CEP existente

99999-999 passa em qualquer regex de CEP; para saber se ele existe, é preciso consultar. Em outubro de 2026, por exemplo, a ViaCEP respondia a esse código com o campo erro. As APIs públicas mais usadas documentam as respostas:

  • ViaCEP: responde com HTTP 400 quando o formato é inválido e com o campo erro quando o CEP de oito dígitos não é encontrado.
  • BrasilAPI: oferece um endpoint de CEP; confira na documentação como ele responde a um código inexistente.

Trate três resultados de forma separada: formato inválido (peça para corrigir), CEP não encontrado (peça para conferir e permita preencher o endereço à mão) e serviço indisponível (não bloqueie o cadastro por causa disso). O guia como usar uma API de CEP detalha a integração, inclusive o cuidado com o volume de consultas.

Checagem rápida pela faixa do estado

Se o formulário também pede a UF, compare os primeiros dígitos do CEP com a faixa do estado. É uma checagem barata, feita sem rede, que pega erros como um CEP do Rio de Janeiro num endereço de São Paulo. A tabela completa está na faixa de CEP por estado.

// Faixas gerais por estado publicadas pelos Correios (há exceções)
const FAIXAS = {
  SP: [['01000000', '19999999']],
  RJ: [['20000000', '28999999']],
  DF: [['70000000', '72799999'], ['73000000', '73699999']],
  // ...demais estados
}

function cepCombinaComUf(cep, uf) {
  const digitos = cep.replace(/[^0-9]/g, '')
  return digitos.length === 8 &&
    (FAIXAS[uf] || []).some(([inicio, fim]) => digitos >= inicio && digitos <= fim)
}

Com oito dígitos dos dois lados, a comparação de texto funciona; por isso a função recusa qualquer outro tamanho, como um CEP que perdeu o zero inicial. Use o resultado como aviso, não como bloqueio: as faixas são gerais, e há exceções, como grandes usuários e caixas postais.

Exemplos em JavaScript, PHP e Python

Os três exemplos validam o formato e depois consultam a ViaCEP, devolvendo um status que a interface pode usar para decidir a mensagem.

JavaScript

const CEP_REGEX = /^[0-9]{5}-?[0-9]{3}$/

async function validarCep(entrada) {
  const texto = entrada.trim()
  if (!CEP_REGEX.test(texto)) return { status: 'formato-invalido' }

  const cep = texto.replace('-', '')
  try {
    const resposta = await fetch('https://viacep.com.br/ws/' + cep + '/json/')
    if (!resposta.ok) return { status: 'indisponivel' }
    const dados = await resposta.json()
    if (dados.erro) return { status: 'nao-encontrado' }
    return { status: 'ok', cep: cep.slice(0, 5) + '-' + cep.slice(5), dados }
  } catch (erro) {
    return { status: 'indisponivel' }
  }
}

PHP

<?php
function validarCep(string $entrada): array
{
    $texto = trim($entrada);
    if (!preg_match('/^[0-9]{5}-?[0-9]{3}$/', $texto)) {
        return ['status' => 'formato-invalido'];
    }

    $cep = str_replace('-', '', $texto);
    $contexto = stream_context_create(['http' => ['timeout' => 5]]);
    $corpo = @file_get_contents('https://viacep.com.br/ws/' . $cep . '/json/', false, $contexto);
    $dados = $corpo === false ? null : json_decode($corpo, true);

    if (!is_array($dados)) {
        return ['status' => 'indisponivel'];
    }
    if (isset($dados['erro'])) {
        return ['status' => 'nao-encontrado'];
    }
    return ['status' => 'ok', 'dados' => $dados];
}

Python

import json
import re
import urllib.error
import urllib.request

CEP_REGEX = re.compile(r'[0-9]{5}-?[0-9]{3}')


def validar_cep(entrada: str) -> dict:
    texto = entrada.strip()
    if not CEP_REGEX.fullmatch(texto):
        return {'status': 'formato-invalido'}

    cep = texto.replace('-', '')
    url = f'https://viacep.com.br/ws/{cep}/json/'
    try:
        with urllib.request.urlopen(url, timeout=5) as resposta:
            dados = json.load(resposta)
    except (urllib.error.URLError, TimeoutError, ValueError):
        return {'status': 'indisponivel'}

    if 'erro' in dados:
        return {'status': 'nao-encontrado'}
    return {'status': 'ok', 'dados': dados}

No Python, fullmatch exige que a string inteira corresponda, então a regex não precisa de ^ e $. No PHP, preg_match devolve 1 quando há correspondência e 0 quando não há.

CEPs para testar

Casos de teste para o campo de CEP

Casos de teste para o campo de CEP
01001-000Resultado esperado: Formato válido; CEP existente (Praça da Sé, São Paulo-SP)
01001000Resultado esperado: Formato válido sem hífen
1001000Resultado esperado: Inválido: sete dígitos, o zero foi perdido
01.001-000Resultado esperado: Recusado pela regex; aceito se você normalizar antes
99999-999Resultado esperado: Formato válido; a existência depende da consulta
0100A-000Resultado esperado: Inválido: contém letra

Para testes com endereços reais de vários estados, o gerador de CEP sorteia CEPs que existem no catálogo do RuaCEP. Use esses códigos só em testes, nunca como o endereço de alguém. Se os CEPs vêm de uma planilha, veja antes como formatar CEP no Excel para não perder o zero.

Perguntas frequentes

Qual é a regex de CEP?

^[0-9]{5}-?[0-9]{3}$, que aceita o CEP com ou sem hífen. Se você já removeu o hífen, use ^[0-9]{8}$.

A regex garante que o CEP existe?

Não. Ela confere só a escrita. Para saber se o código existe, consulte uma base de CEPs, como uma API ou a busca dos Correios.

Devo guardar o CEP com ou sem hífen no banco de dados?

Uma prática comum é guardar os oito dígitos como texto e mostrar o hífen na tela. Nunca use um tipo numérico, que perde o zero à esquerda.

Qual inputmode e autocomplete usar no campo de CEP?

inputmode="numeric" para o teclado numérico e autocomplete="postal-code" para o preenchimento automático do navegador.

Fontes

Compartilhe esta página

Copie o link ou escolha onde enviar esta informação.