Artigo Build·Desenvolvimento·13 min de leitura

Regex para Validação de Formulários: E-mail, CPF, Telefone e Mais

Coleção prática de regex prontas para os campos mais comuns em formulários web brasileiros — testadas, com versões para front e back, integração com React, máscara de input em tempo real e os erros que destroem UX.

Vitor Morais

Por Vitor Morais

Fundador do MochaLabz ·

Teste suas regex online

Destaque de correspondências em tempo real, com explicação de cada parte da regex.

Testar regex →

Regex em formulários é a primeira linha de defesa contra entrada inválida — e a primeira fonte de UX ruim quando mal feita. Esta coleção cobre os campos mais comuns em formulários brasileiros, com regex testadas para formato com ou sem máscara, integração com React, máscara em tempo real via input mask e os limites do que regex pode (e não pode) validar.

Quando usar regex (e quando não usar)

Regex é boa ferramenta? Depende do tipo de validação
CritérioUse regex?Por quê
Formato de e-mail✅ SimDetecta erro de digitação rápido no front
E-mail existe / é entregável❌ NãoUse lookup MX + verificação por envio
CPF formatado✅ SimPara máscara visual
CPF realmente válido❌ NãoUse algoritmo de módulo 11
CNPJ formatado✅ SimIdem
Telefone brasileiro✅ SimPadrões claros, regex resolve
Telefone internacional❌ NãoUse libphonenumber-js
CEP✅ Sim8 dígitos, simples
Senha forte (regras)✅ SimLookahead checa regras compostas
Cartão de crédito válido⚠️ ParcialRegex para formato + Luhn para validade
URL realmente acessível❌ NãoFaça HEAD request

E-mail

// ✅ Balanceada — cobre 99% dos e-mails reais const EMAIL = /^[^\s@]+@[^\s@]+\.[^\s@]+$/; // ✅ Mais restrita (sem caracteres especiais raros) const EMAIL_STRICT = /^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$/; function validarEmail(input: string): boolean { const trimmed = input.trim().toLowerCase(); if (trimmed.length > 254) return false; // RFC max return EMAIL.test(trimmed); } validarEmail('ana@gmail.com'); // true validarEmail('user+tag@gmail.com'); // true (plus addressing OK) validarEmail('user@dominio.museum'); // true (TLD longo OK) validarEmail('user@@gmail.com'); // false

Por que essa regex e não outra

A regex 100% RFC 5322 tem 6 000 caracteres e aceita coisas que servidores reais rejeitam. Esta versão cobre 99% dos casos práticos sem aceitar entrada absurda. Para mais detalhes, veja o que torna um e-mail válido.

CPF (com ou sem máscara)

// ✅ Aceita 000.000.000-00 ou 00000000000 const CPF_FORMAT = /^(\d{3}\.?\d{3}\.?\d{3}-?\d{2})$/; // Para validação real (formato + dígitos verificadores): function validarCPF(input: string): boolean { const cpf = input.replace(/\D/g, ''); if (cpf.length !== 11) return false; if (/^(\d)\1{10}$/.test(cpf)) return false; // todos iguais // Cálculo de módulo 11 dos dois DVs const calcDV = (base: string, peso0: number) => { const soma = [...base].reduce((s, d, i) => s + Number(d) * (peso0 - i), 0); const r = soma % 11; return r < 2 ? 0 : 11 - r; }; const dv1 = calcDV(cpf.slice(0, 9), 10); const dv2 = calcDV(cpf.slice(0, 10), 11); return dv1 === Number(cpf[9]) && dv2 === Number(cpf[10]); }

CNPJ (com ou sem máscara)

// ✅ Aceita 00.000.000/0000-00 ou 00000000000000 const CNPJ_FORMAT = /^(\d{2}\.?\d{3}\.?\d{3}\/?\d{4}-?\d{2})$/; // CNPJ ALFANUMÉRICO (a partir de jul/2026) // Os 12 primeiros podem ser A-Z ou 0-9; os 2 últimos sempre dígitos const CNPJ_ALFA = /^([A-Z0-9]{2}\.?[A-Z0-9]{3}\.?[A-Z0-9]{3}\/?[A-Z0-9]{4}-?\d{2})$/;

Telefone brasileiro

// ✅ Celular: (11) 99999-9999, 11999999999, 11 9 9999-9999 const CELULAR = /^\(?\d{2}\)?\s?9\s?\d{4}[-\s]?\d{4}$/; // ✅ Fixo: (11) 3333-3333, 1133333333 const FIXO = /^\(?\d{2}\)?\s?[2-8]\d{3}[-\s]?\d{4}$/; // ✅ Aceita celular ou fixo const TELEFONE = /^\(?\d{2}\)?\s?9?\s?\d{4}[-\s]?\d{4}$/; // ✅ Internacional (E.164): +5511987654321 const INTERNACIONAL = /^\+[1-9]\d{1,14}$/; function validarTelefone(input: string): boolean { const limpo = input.replace(/\D/g, ''); // Brasil: 10 dígitos (fixo) ou 11 (celular) return limpo.length === 10 || limpo.length === 11; } // Para internacional sério, use libphonenumber-js do Google import { isValidPhoneNumber } from 'libphonenumber-js'; isValidPhoneNumber('+5511987654321'); // true

CEP

// ✅ 00000-000 ou 00000000 const CEP = /^\d{5}-?\d{3}$/; function validarCEP(input: string): boolean { const cep = input.replace(/\D/g, ''); return /^\d{8}$/.test(cep); } // Para verificar se o CEP existe, consulte a API ViaCEP: async function buscarCEP(cep: string) { const limpo = cep.replace(/\D/g, ''); const r = await fetch(`https://viacep.com.br/ws/${limpo}/json/`); const data = await r.json(); return data.erro ? null : data; }

URL

// ✅ Cobre http(s), com ou sem path/query/hash const URL_REGEX = /^(https?:\/\/)?([\w-]+\.)+[\w]{2,}(\/[\w-./?%&=#]*)?$/; // ✅ HTTPS obrigatório (para apps modernas) const URL_HTTPS = /^https:\/\/[\w-]+(\.[\w-]+)+(\/[\w-./?%&=#]*)?$/; // ✅ Em vez de regex, use a API URL do JS (mais robusta) function validarURL(input: string): boolean { try { const url = new URL(input); return ['http:', 'https:'].includes(url.protocol); } catch { return false; } }

Data brasileira (DD/MM/AAAA) e ISO

// ✅ Formato brasileiro DD/MM/AAAA const DATA_BR = /^(0[1-9]|[12]\d|3[01])\/(0[1-9]|1[0-2])\/\d{4}$/; // ✅ ISO 8601: AAAA-MM-DD const DATA_ISO = /^\d{4}-(0[1-9]|1[0-2])-(0[1-9]|[12]\d|3[01])$/; // ATENÇÃO: regex valida formato, não data real (31/02/2026 passa) function validarData(input: string): boolean { if (!DATA_BR.test(input)) return false; const [d, m, y] = input.split('/').map(Number); const dt = new Date(y, m - 1, d); return dt.getDate() === d && dt.getMonth() === m - 1 && dt.getFullYear() === y; } validarData('29/02/2024'); // true (bissexto) validarData('29/02/2025'); // false (não bissexto) validarData('31/04/2026'); // false (abril tem 30 dias)

Senha forte (com lookahead)

// ✅ Mínimo 8 chars, 1 maiúscula, 1 minúscula, 1 dígito const SENHA_MEDIA = /^(?=.*[a-z])(?=.*[A-Z])(?=.*\d).{8,}$/; // ✅ Mínimo 12 chars + símbolo obrigatório const SENHA_FORTE = /^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[!@#$%^&*()_+\-={}|:;<>?,.]).{12,}$/; // Cada (?=...) é um LOOKAHEAD: não consome chars, só verifica // que o padrão existe em algum lugar da string. // ⚠️ Recomendação 2026 (NIST): foque em comprimento (12+), // não em complexidade. Veja artigo de entropia de senha.

Senha: mais é mais

O NIST recomenda focar em comprimento (mínimo 12 caracteres, ideal 16+) em vez de regras complexas que geram senhas previsíveis tipo Senha@2026!. Passphrases longas com entropia alta vencem senhas curtas com caracteres especiais.

Cartão de crédito

// ✅ Apenas formato (13–19 dígitos com ou sem espaço/hífen) const CARTAO_FORMAT = /^[\d\s-]{13,23}$/; // Por bandeira: const VISA = /^4\d{12}(\d{3})?$/; const MASTER = /^5[1-5]\d{14}$/; const AMEX = /^3[47]\d{13}$/; const ELO = /^(636368|438935|504175|451416|636297|5067|4576|4011)\d{10,12}$/; // Para validação REAL, use algoritmo de Luhn (mod 10): function validarLuhn(numero: string): boolean { const digits = numero.replace(/\D/g, '').split('').reverse().map(Number); const soma = digits.reduce((acc, d, i) => { if (i % 2 === 1) { const dobrado = d * 2; return acc + (dobrado > 9 ? dobrado - 9 : dobrado); } return acc + d; }, 0); return soma % 10 === 0; }

Apenas letras / apenas números / alfanumérico

// ✅ Apenas letras (sem números, sem espaço) const SO_LETRAS = /^[a-zA-Z]+$/; // ✅ Letras + acentos PT-BR const LETRAS_PT = /^[a-zA-ZÀ-ÿ\s]+$/; // ✅ Apenas dígitos const SO_NUMEROS = /^\d+$/; // ✅ Alfanumérico const ALFANUM = /^[a-zA-Z0-9]+$/; // ✅ Slug (kebab-case minúsculas) const SLUG = /^[a-z0-9]+(-[a-z0-9]+)*$/;

Máscara de input em tempo real

Regex valida; máscara formata enquanto o usuário digita. A combinação dá UX profissional. Exemplos puros em JavaScript:

// Máscara CPF: 00000000000 → 000.000.000-00 function maskCPF(value: string): string { return value .replace(/\D/g, '') .slice(0, 11) .replace(/(\d{3})(\d)/, '$1.$2') .replace(/(\d{3})\.(\d{3})(\d)/, '$1.$2.$3') .replace(/(\d{3})\.(\d{3})\.(\d{3})(\d)/, '$1.$2.$3-$4'); } // Máscara CEP: 00000000 → 00000-000 function maskCEP(value: string): string { return value .replace(/\D/g, '') .slice(0, 8) .replace(/(\d{5})(\d)/, '$1-$2'); } // Máscara telefone: 11999999999 → (11) 99999-9999 function maskTelefone(value: string): string { return value .replace(/\D/g, '') .slice(0, 11) .replace(/(\d{2})(\d)/, '($1) $2') .replace(/(\d{5})(\d)/, '$1-$2'); } // Em React: <input value={cpf} onChange={(e) => setCpf(maskCPF(e.target.value))} maxLength={14} placeholder="000.000.000-00" />

Bibliotecas de máscara recomendadas

Bibliotecas de input mask em 2026
CritérioBundle sizeReact?Quando usar
IMask~30 KBSim (react-imask)Padrão atual, super flexível
Cleave.js~14 KBSim (cleave-react)Bom equilíbrio, fácil
react-input-mask~10 KBSimSimples, bem testada em React
react-number-format~25 KBSimÓtima para valores monetários
Custom (sem lib)0SimCasos simples (CPF, CEP, telefone BR)

Validação no React (padrão react-hook-form)

// npm install react-hook-form import { useForm } from 'react-hook-form'; const REGEX = { email: /^[^\s@]+@[^\s@]+\.[^\s@]+$/, cpf: /^\d{3}\.?\d{3}\.?\d{3}-?\d{2}$/, telefone: /^\(?\d{2}\)?\s?9?\s?\d{4}[-\s]?\d{4}$/, cep: /^\d{5}-?\d{3}$/, }; export default function Cadastro() { const { register, handleSubmit, formState: { errors } } = useForm(); return ( <form onSubmit={handleSubmit(console.log)}> <input {...register('email', { required: 'E-mail é obrigatório', pattern: { value: REGEX.email, message: 'Formato inválido' }, })} /> {errors.email && <p>{errors.email.message as string}</p>} <input {...register('cpf', { required: true, pattern: { value: REGEX.cpf, message: 'CPF inválido' }, // Validação custom além da regex validate: (v) => validarCPF(v) || 'CPF não passa no algoritmo', })} /> </form> ); }

HTML5 nativo: defesa em profundidade gratuita

Antes mesmo de regex em JS, o navegador já valida via atributos HTML5. Combine com regex para defesa em profundidade:

<form> <input type="email" name="email" required autoComplete="email" pattern="[^\s@]+@[^\s@]+\.[^\s@]+" /> <input type="tel" name="telefone" required pattern="\(?\d{2}\)?\s?9?\s?\d{4}[-\s]?\d{4}" inputMode="numeric" /> <input type="password" name="senha" required minLength={12} pattern="(?=.*[a-z])(?=.*[A-Z])(?=.*\d).{12,}" /> </form> <!-- type=email + required = validação básica grátis pattern adiciona regex custom inputMode=numeric mostra teclado correto no mobile -->

Cuidado com ReDoS (Regex DoS)

Regex maliciosa pode derrubar seu servidor

Padrões com backtracking exponencial podem travar o servidor por minutos com input adversarial. Padrões a evitar: (a+)+, (a|aa)*, (.*)*. Sempre valide tamanho do input antes de aplicar regex e considere lib safe-regex para auditoria.

// ❌ Vulnerável a ReDoS const PERIGO = /^(\w+)*$/; PERIGO.test('aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa!'); // → trava por minutos // ✅ Seguro: limite de tamanho function validarSeguro(input: string): boolean { if (input.length > 200) return false; // bound return SAFE_REGEX.test(input); } // ✅ Auditoria automática // npm install safe-regex import safe from 'safe-regex'; safe(PERIGO); // false

Diferenças entre engines (JS, Python, Postgres)

Recursos de regex por engine (2026)
CritérioJavaScriptPythonPostgreSQL
Lookahead (?=...)✅ (POSIX ext)
Lookbehind (?<=...)✅ (ES2018+)
Named groups (?<name>)✅ (?P<name>)✅ (?P<name>)
Unicode property \p{...}✅ flag u
Atomic groups (?>...)✅ (3.11+)
Possessive quantifiers
Flag insensitivei(?i) ou flagcase-insensitive op (~*)

Erros comuns na validação por regex

Anti-padrões frequentes

  • Validar só no front: qualquer cliente burla. Sempre valide no backend.
  • Confiar só na regex para CPF/CNPJ: regex valida formato, não dígitos verificadores.
  • Bloquear plus addressing em e-mail: irrita usuários técnicos sem benefício real.
  • Esquecer de normalizar antes de validar: espaços, case, unicode podem fazer regex falhar.
  • Regex muito permissiva: .* aceita qualquer coisa.
  • Regex muito restritiva: rejeita entrada válida, frustra usuário.
  • Mensagem de erro genérica: “CPF inválido” sem dizer o que está errado.
  • Aplicar regex em string longa sem bound: vetor de ReDoS.

Checklist da validação de formulário ideal

  • ✅ Máscara em tempo real (UX) + regex no submit (validação).
  • ✅ HTML5 nativo (type, required, pattern) como fallback.
  • ✅ Validação em duas camadas: front (UX) + back (segurança).
  • ✅ Algoritmo específico além da regex (módulo 11 para CPF/CNPJ, Luhn para cartão).
  • ✅ Mensagens de erro claras e acionáveis.
  • ✅ Limite de tamanho do input antes de aplicar regex.
  • ✅ Auditoria de regex contra ReDoS (safe-regex).
  • ✅ Aceita formato com e sem máscara (limpa antes de validar).
  • ✅ Para telefone internacional, libphonenumber-js.
  • ✅ Para CEP, integração com ViaCEP para auto-preencher endereço.

Para fundamentos de regex, veja o guia completo de expressões regulares; para recursos avançados (lookahead, named groups), veja grupos de captura em regex.

Perguntas frequentes

Regex valida CPF de verdade?+

Não. Regex valida apenas o FORMATO (11 dígitos com ou sem máscara). Para validar se o CPF é matematicamente válido, é preciso aplicar o algoritmo de módulo 11 nos dígitos verificadores. Use regex no front para feedback imediato e o algoritmo no submit para garantia. CPFs como 111.111.111-11 passam em qualquer regex de formato e são inválidos pela Receita.

Posso usar a mesma regex no front-end e no back-end?+

Sim e deve. Defesa em profundidade exige validação nos dois lados. Mas atenção a diferenças entre engines: regex JavaScript, Python, Go e Postgres têm comportamentos sutis (lookbehind, named groups, escapes). Para regex compartilhada cross-stack, prefira sintaxe POSIX básica e teste em cada ambiente. Lib como xregexp ajuda em casos avançados.

Devo usar regex ou input mask para formatação?+

Use os dois para coisas diferentes. Input mask (Cleave.js, react-input-mask, IMask) formata enquanto o usuário digita — UX melhor. Regex valida no submit ou ao perder foco. Mask sem regex pode aceitar entrada incompleta; regex sem mask gera UX ruim. A combinação dos dois é o padrão profissional.

Por que minha regex de e-mail rejeita endereços válidos?+

A regex perfeita conforme RFC 5322 tem 6000+ caracteres e é inviável. As regex curtas costumam rejeitar plus addressing (user+tag@gmail.com), aspas, IPs literais. A escolha pragmática é usar /^[^\s@]+@[^\s@]+\.[^\s@]+$/ — cobre 99% dos casos reais sem aceitar lixo. Para garantia total, faça verificação por envio de e-mail.

Regex em JavaScript é case-sensitive por padrão?+

Sim. Para case-insensitive, adicione a flag i: /^[a-z0-9]+$/i. Outras flags úteis: g (global, encontra todas), m (multiline, ^ e $ valem por linha), s (dotall, . inclui \n), u (Unicode), y (sticky). Pode combinar: /padrao/gim. Sem flag, comportamento é o mais restritivo.

Como evitar ataques ReDoS com regex maliciosa?+

ReDoS (Regular Expression Denial of Service) acontece quando regex com backtracking exponencial bate em input adversarial. Padrões a evitar: (a+)+ , (a|aa)*, (.*)*. Use regex sem backtracking quando possível, valide tamanho máximo do input antes de aplicar regex, considere lib como safe-regex para auditoria. No back-end, configure timeout em validações.

Validação HTML5 nativa substitui regex?+

Para casos simples, sim. <input type="email" required> e atributos pattern, minLength, maxLength fazem validação básica sem JavaScript. Mas mensagens de erro padrão do navegador são limitadas e a UX não é tão boa quanto validação custom. Em produção, geralmente combina HTML5 (defesa em profundidade) + JS + backend.

Telefone brasileiro precisa de validação especial?+

Sim. Celulares brasileiros têm 11 dígitos (DDD + 9 + 8 dígitos), telefones fixos têm 10 dígitos. O 9 inicial em celulares foi adicionado em 2014 — números antigos podem ter 10 dígitos. Para apps que aceitam telefone internacional, use libs como libphonenumber-js (Google) que entende 200+ países e formata corretamente.

#regex#formulários#validação#javascript#react#html5#cpf#cnpj#telefone#máscara input

Continue lendo