APRENDIZADO & COGNIÇÃO
Tudo que você precisa saber sobre CEP em JavaScript: validação, máscara, API e debounce
Quem já construiu um formulário de endereço no Brasil sabe que o campo de CEP parece simples, mas esconde algumas…

Quem já construiu um formulário de endereço no Brasil sabe que o campo de CEP parece simples, mas esconde algumas armadilhas. Neste artigo, vou cobrir do básico ao avançado: estrutura do CEP, validação com Regex, máscara de input, integração com a API ViaCEP, tratamento de erros e debounce — tudo em JavaScript puro, sem dependências externas.
1. O que é o CEP e como ele é estruturado?
O CEP (Código de Endereçamento Postal) é composto por 8 dígitos numéricos, divididos em dois grupos:
XXXXX - XXX | |Faixa Sufixo
- Os 5 primeiros dígitos identificam a localidade (região, cidade, bairro)
- Os 3 últimos dígitos identificam o logradouro ou setor específico
Exemplos reais:
| CEP | Local |
|---|---|
| 01001-000 | Praça da Sé, São Paulo – SP |
| 20040-020 | Centro, Rio de Janeiro – RJ |
| 30112-000 | Centro, Belo Horizonte – MG |
Detalhe importante: nem todo CEP aponta para uma rua específica. Grandes empresas e órgãos públicos possuem CEPs exclusivos (ex: Correios, Receita Federal).
2. Limpeza de Input (o primeiro passo que muitos pulam)
Antes de qualquer validação, precisamos normalizar o que o usuário digitou — removendo traços, espaços e qualquer caractere não numérico:
function limparCEP(cep) { return cep.replace(/D/g, "");}// Exemplos:limparCEP("01001-000"); // "01001000"limparCEP("01001 000"); // "01001000"limparCEP(" 01001000 "); // "01001000"
A regex /D/g substitui qualquer caractere que não seja dígito (D) por string vazia, globalmente (g).
3. Aplicando Máscara ao Input
Para formatar o CEP enquanto o usuário digita (no formato XXXXX-XXX):
function formatarCEP(cep) { const cepLimpo = limparCEP(cep); // Aplica máscara apenas quando tiver 8 dígitos completos if (cepLimpo.length === 8) { return cepLimpo.replace(/^(d{5})(d{3})$/, "$1-$2"); } return cepLimpo;}console.log(formatarCEP("01001000")); // "01001-000"console.log(formatarCEP("0100100")); // "0100100" (incompleto, sem máscara)
Para aplicar a máscara em tempo real no input HTML:
const inputCEP = document.getElementById("cep");inputCEP.addEventListener("input", (e) => { const cepLimpo = limparCEP(e.target.value); // Limita a 8 dígitos const cepTruncado = cepLimpo.slice(0, 8); // Aplica a máscara progressiva enquanto digita if (cepTruncado.length > 5) { e.target.value = cepTruncado.replace(/^(d{5})(d{0,3})$/, "$1-$2"); } else { e.target.value = cepTruncado; }});
Por que máscara progressiva? Se você só aplicar a máscara quando tiver 8 dígitos, o usuário vê
01001000e só depois01001-000. Com a máscara progressiva, ele vê01001-assim que digita o 6º caractere — UX muito melhor.
4. Validação com Regex (antes de chamar qualquer API)
Nunca dispare uma requisição para a API sem validar antes. Isso economiza requisições desnecessárias e protege sua aplicação:
function validarCEP(cep) { const cepLimpo = limparCEP(cep); // Deve ter exatamente 8 dígitos numéricos const regexCEP = /^[0-9]{8}$/; return regexCEP.test(cepLimpo);}console.log(validarCEP("01001-000")); // trueconsole.log(validarCEP("00000-000")); // true (formalmente válido, mas pode não existir)console.log(validarCEP("0100100")); // false (só 7 dígitos)console.log(validarCEP("abcde-fgh")); // false
Atenção: A validação via Regex apenas verifica o formato, não se o CEP existe de fato nos Correios. Para isso, precisamos consultar a API.
5. Integrando com a API ViaCEP
O ViaCEP é um serviço gratuito, sem autenticação e muito confiável para consultas de CEP no Brasil.
async function consultarCEP(cep) { const cepLimpo = limparCEP(cep); // Validação antes da requisição if (!validarCEP(cepLimpo)) { throw new Error("Formato de CEP inválido."); } const response = await fetch(`https://viacep.com.br/ws/${cepLimpo}/json/`); if (!response.ok) { throw new Error(`Erro HTTP: ${response.status}`); } const data = await response.json(); // A API retorna { "erro": true } quando o CEP não existe if (data.erro) { throw new Error("CEP não encontrado."); } return data;}
Estrutura do objeto retornado pela API:
{ "cep": "01001-000", "logradouro": "Praça da Sé", "complemento": "lado ímpar", "bairro": "Sé", "localidade": "São Paulo", "uf": "SP", "ibge": "3550308", "ddd": "11", "siafi": "7107"}
Usando a função com tratamento de erros:
async function preencherEndereco(cep) { try { const endereco = await consultarCEP(cep); document.getElementById("logradouro").value = endereco.logradouro; document.getElementById("bairro").value = endereco.bairro; document.getElementById("cidade").value = endereco.localidade; document.getElementById("uf").value = endereco.uf; } catch (error) { console.error("Falha ao buscar CEP:", error.message); // Aqui você pode mostrar uma mensagem de erro na UI }}
6. Debounce: a técnica que evita spam de requisições
Um erro clássico é disparar a busca a cada tecla digitada. Com debounce, você espera o usuário parar de digitar antes de fazer a requisição:
function debounce(fn, delay = 500) { let timer; return function (...args) { clearTimeout(timer); timer = setTimeout(() => fn.apply(this, args), delay); };}// Cria uma versão "debounced" da função de buscaconst buscarCEPComDebounce = debounce(async (cep) => { if (validarCEP(cep)) { await preencherEndereco(cep); }}, 600);// Conecta ao inputdocument.getElementById("cep").addEventListener("input", (e) => { buscarCEPComDebounce(e.target.value);});
onBlur vs debounce — quando usar cada um?
| Situação | Recomendação |
|---|---|
| Formulário simples, usuário já sabe o CEP | onBlur (mais simples, menos requisições) |
| UX fluida, preenchimento automático rápido | debounce no onChange (300–600ms) |
| Mobile com teclado numérico | onBlur (evita busca durante digitação lenta) |
7. Feedback visual durante o carregamento
Um detalhe que melhora muito a UX: mostrar ao usuário que a busca está acontecendo:
async function buscarComFeedback(cep) { const input = document.getElementById("cep"); const status = document.getElementById("cep-status"); if (!validarCEP(cep)) return; // Estado de carregamento input.disabled = true; status.textContent = "Buscando..."; try { const endereco = await consultarCEP(cep); preencherCampos(endereco); status.textContent = "✓ Endereço encontrado"; status.style.color = "green"; } catch (error) { status.textContent = "✗ " + error.message; status.style.color = "red"; } finally { // Sempre reabilita o input, mesmo em caso de erro input.disabled = false; }}
8. Tratamento de edge cases que passam despercebidos
// CEPs que formalmente são válidos mas costumam não existir:const CEPS_INVALIDOS_CONHECIDOS = [ "00000000", // Zerado "11111111", // Sequência repetida "99999999", // Máximo];function cepSuspeito(cep) { const limpo = limparCEP(cep); // Sequências repetidas (00000000, 11111111, etc.) if (/^(d)1{7}$/.test(limpo)) return true; return CEPS_INVALIDOS_CONHECIDOS.includes(limpo);}console.log(cepSuspeito("00000000")); // trueconsole.log(cepSuspeito("12345678")); // falseconsole.log(cepSuspeito("01001000")); // false
Solução Completa — Juntando tudo
// utils/cep.jsconst limparCEP = (cep) => cep.replace(/D/g, "");const formatarCEP = (cep) => { const limpo = limparCEP(cep).slice(0, 8); return limpo.length > 5 ? limpo.replace(/^(d{5})(d{0,3})$/, "$1-$2") : limpo;};const validarCEP = (cep) => /^[0-9]{8}$/.test(limparCEP(cep));const consultarCEP = async (cep) => { const limpo = limparCEP(cep); if (!validarCEP(limpo)) throw new Error("CEP inválido"); const res = await fetch(`https://viacep.com.br/ws/${limpo}/json/`); if (!res.ok) throw new Error(`Erro ${res.status}`); const data = await res.json(); if (data.erro) throw new Error("CEP não encontrado"); return data;};const debounce = (fn, ms = 500) => { let t; return (...args) => { clearTimeout(t); t = setTimeout(() => fn(...args), ms); };};export { limparCEP, formatarCEP, validarCEP, consultarCEP, debounce };
Conclusão
Um campo de CEP bem implementado combina:
- Limpeza — normaliza o input antes de qualquer processamento
- Máscara progressiva — melhora a UX durante a digitação
- Validação local — evita requisições desnecessárias
- Debounce inteligente — escolha baseada no contexto (onBlur vs onChange)
- Tratamento de erros robusto — distingue erro de rede de CEP inexistente
- Feedback visual — mantém o usuário informado durante a busca
Para testar rapidamente um CEP durante o desenvolvimento sem montar um formulário inteiro, ferramentas como o cep-br.com permitem validar e consultar o retorno da API de forma direta — útil para debugar edge cases antes de integrar no código.
Pergunta para a comunidade: Vocês costumam usar onBlur ou debounce no onChange nos formulários de vocês? E quando o CEP não é encontrado, vocês limpam os campos de endereço ou mantêm o que estava preenchido antes? Qual abordagem tem funcionado melhor na UX de vocês?
Fonte: TabNews
Perguntas frequentes
Respostas institucionais sobre esta cobertura, fontes e limites editoriais.
- Sobre o que trata “Tudo que você precisa saber sobre CEP em JavaScript: validação,…”?
- Quem já construiu um formulário de endereço no Brasil sabe que o campo de CEP parece simples, mas esconde algumas armadilhas. Neste artigo, vou cobrir do básico ao avançado: estrutura do CEP, validação com Regex, máscara…
- 1. O que é o CEP e como ele é estruturado?
- O CEP (Código de Endereçamento Postal) é composto por 8 dígitos numéricos, divididos em dois grupos:XXXXX - XXX | |Faixa SufixoOs 5 primeiros dígitos identificam a localidade (região, cidade, bairro)Os 3 últimos dígitos identificam o logradouro ou setor específicoExemplos reais:CEPLocal01001-000Praça da Sé,…
- O que a matéria explica sobre “2. Limpeza de Input (o primeiro passo que muitos pulam)”?
- Antes de qualquer validação, precisamos normalizar o que o usuário digitou — removendo traços, espaços e qualquer caractere não numérico:function limparCEP(cep) { return cep.replace(/D/g, "");}// Exemplos:limparCEP("01001-000"); // "01001000"limparCEP("01001 000"); // "01001000"limparCEP(" 01001000 "); // "01001000"A regex /D/g substitui qualquer caractere que não…
- O que a matéria explica sobre “3. Aplicando Máscara ao Input”?
- Para formatar o CEP enquanto o usuário digita (no formato XXXXX-XXX):function formatarCEP(cep) { const cepLimpo = limparCEP(cep); // Aplica máscara apenas quando tiver 8 dígitos completos if (cepLimpo.length === 8) { return cepLimpo.replace(/^(d{5})(d{3})$/, "$1-$2"); } return cepLimpo;}console.log(formatarCEP("01001000")); // "01001-000"console.log(formatarCEP("0100100")); // "0100100" (incompleto,…
- O que a matéria explica sobre “4. Validação com Regex (antes de chamar qualquer API)”?
- Nunca dispare uma requisição para a API sem validar antes. Isso economiza requisições desnecessárias e protege sua aplicação:function validarCEP(cep) { const cepLimpo = limparCEP(cep); // Deve ter exatamente 8 dígitos numéricos const regexCEP = /^[0-9]{8}$/; return regexCEP.test(cepLimpo);}console.log(validarCEP("01001-000")); // trueconsole.log(validarCEP("00000-000")); // true (formalmente…
- Quem edita o conteúdo do Estrato Mente?
- A cobertura é produzida e revisada pela equipe editorial do Estrato Mente, com editor-chefe responsável pela linha editorial. Conheça a redação em https://mente.estrato.cc/equipe/.
- Quais fontes o Estrato prioriza?
- Priorizamos fontes primárias (órgãos oficiais, balanços, estudos revisados, documentos públicos) e cruzamos informações antes da publicação, conforme a política editorial.
- Como solicitar correção de uma matéria?
- Envie o pedido pela página de Correções (https://mente.estrato.cc/correcoes/) ou Contato (https://mente.estrato.cc/contato/). Erros materiais são corrigidos com registro da atualização.