Ir para conteúdo

Regex: o que é, como funciona e como validar e-mail e telefone sem dor de cabeça

Monitor exibindo código de programação, representando o uso de expressões regulares (regex) para validar dados

Se você já abriu um projeto e encontrou algo como /^\(\d{2}\) \d{4,5}-\d{4}$/, deve ter feito a mesma pergunta que todo mundo faz: “o que são essas letras e símbolos e por que alguém escreveria isso de propósito?”. Isso é uma regex, abreviação de regular expression (expressão regular em português), e ela está em praticamente toda linguagem, editor de código e ferramenta de busca que você usa.

O problema é que a maioria dos tutoriais ou joga uma tabela de símbolos sem explicar nada, ou entrega uma regex pronta para copiar e colar. Aí o formulário valida errado, rejeita um e-mail legítimo, aceita um telefone quebrado e ninguém sabe por quê. Pior: uma regex mal escrita pode até derrubar um servidor.

Este guia explica o que regex realmente é, como ler cada pedaço de uma expressão, como validar e-mail e telefone na prática com JavaScript, quais erros evitar e, principalmente, quando não usar regex. A ideia é você sair daqui entendendo o que escreve, não só copiando.

O que é regex, tecnicamente

Regex é um padrão de texto que um interpretador usa para procurar, validar ou substituir trechos dentro de uma string. Você descreve a “forma” do que procura, e o interpretador diz se o texto bate (o famoso match) ou não.

Pense assim: em vez de escrever dez if para checar se um telefone tem parênteses, dois dígitos, um espaço, cinco dígitos, um traço e mais quatro dígitos, você escreve uma linha que descreve tudo isso de uma vez.

Os usos mais comuns no dia a dia de quem desenvolve sites:

  • Validação de formulário: telefone, CEP, e-mail, placa de carro.
  • Busca e substituição: limpar espaços duplos, remover caracteres inválidos, padronizar formatos.
  • Extração de dados: pegar todos os links, hashtags ou valores em reais de um texto.
  • Roteamento e redirecionamentos: regras de URL em servidores e em ferramentas de SEO técnico.

Quase toda linguagem tem suporte nativo: JavaScript, PHP, Python, Java, Go, C# e até o grep do terminal. A sintaxe tem pequenas variações entre elas, mas a base é a mesma. Neste artigo os exemplos são em JavaScript, que é onde a maioria dos formulários de site roda.

Anatomia de uma regex: os blocos que você precisa conhecer

Uma regex é montada com poucos tipos de bloco. Entendendo cada tipo, qualquer expressão deixa de ser um monstro e vira uma sequência de peças simples.

Caracteres literais e o escape

Letras e números comuns casam com eles mesmos. A regex casa encontra a palavra “casa”. O problema aparece com caracteres que têm significado especial, como ., (, ), ?, + e *. Para tratá-los como texto comum, você coloca uma barra invertida antes. Isso se chama escape:

/\(11\)/   // casa com o texto "(11)", parênteses incluídos
/R\$/      // casa com "R$"
/\./       // casa com um ponto literal

Esquecer o escape é o erro mais comum de quem está começando. Um . sem barra casa com qualquer caractere, não só com ponto.

Caracteres representantes

SímboloO que representa
.Qualquer caractere (exceto quebra de linha)
\dUm dígito, equivalente a [0-9]
\wLetra, número ou underscore
\sEspaço em branco (espaço, tab, quebra de linha)
[abc]Um dos caracteres listados: a, b ou c
[a-z]Qualquer letra minúscula de a até z
[^abc]Qualquer caractere menos a, b e c
(...)Grupo de caracteres, tratado como uma unidade
a|b”a” ou “b”

As versões em maiúscula invertem o sentido: \D é “qualquer coisa que não seja dígito”, \W é “qualquer coisa que não seja letra, número ou underscore” e \S é “qualquer coisa que não seja espaço”.

Quantificadores

Os quantificadores dizem quantas vezes o bloco anterior pode se repetir.

SímboloQuantidade
?Zero ou uma vez
*Zero, uma ou mais vezes
+Uma ou mais vezes
{3}Exatamente 3 vezes
{2,5}De 2 até 5 vezes
{2,}2 vezes ou mais

Um detalhe que costuma gerar confusão: {2} significa exatamente 2, não “no máximo 2”. Se você quer “de 4 a 5 dígitos”, o certo é \d{4,5}.

Âncoras

Âncoras não casam com caracteres, elas marcam posições no texto.

SímboloPosição
^Início do texto (ou da linha, com a flag m)
$Fim do texto (ou da linha, com a flag m)
\bLimite de palavra (início ou fim de uma palavra)

As âncoras ^ e $ são o que separa uma validação de uma busca. Sem elas, a regex só precisa encontrar o padrão em algum lugar da string. Isso gera um bug clássico, que veremos mais adiante.

Flags

As flags ficam depois da barra final da regex e mudam o comportamento da busca:

FlagO que faz
gGlobal: encontra todas as ocorrências, não só a primeira
iIgnora diferença entre maiúscula e minúscula
mFaz ^ e $ funcionarem por linha
sFaz o . casar também com quebra de linha
uAtiva suporte completo a Unicode (acentos, emojis)

Passo a passo: lendo uma regex de telefone

Vamos pegar a regex que abriu o artigo e quebrá-la em peças. O objetivo é validar telefones brasileiros no formato (00) 0000-0000 ou (00) 00000-0000:

/^\(\d{2}\) \d{4,5}-\d{4}$/

Lendo da esquerda para a direita:

  1. ^ : o texto precisa começar aqui.
  2. \( : um parêntese de abertura literal (o escape tira o sentido de “grupo”).
  3. \d{2} : exatamente dois dígitos, o DDD.
  4. \) : um parêntese de fechamento literal.
  5. : um espaço literal. Sim, o espaço faz parte da regex e precisa estar no texto.
  6. \d{4,5} : de quatro a cinco dígitos, o que cobre telefone fixo e celular.
  7. - : um traço literal. Fora de colchetes, o traço não precisa de escape.
  8. \d{4} : exatamente quatro dígitos finais.
  9. $ : o texto precisa terminar aqui.

Testando no console do navegador:

const telefone = /^\(\d{2}\) \d{4,5}-\d{4}$/;

telefone.test("(11) 98787-8787"); // true
telefone.test("(11) 4747-4747");  // true
telefone.test("11 98787-8787");   // false (faltam os parênteses)
telefone.test("(11) 987-8787");   // false (poucos dígitos)

Deixando a regex mais rigorosa

Essa versão aceita um telefone fixo de 5 dígitos, o que não existe. Celulares brasileiros têm nove dígitos e começam com 9; fixos têm oito. Dá para refletir isso na própria regex:

const telefoneRigoroso = /^\(\d{2}\) (9\d{4}|\d{4})-\d{4}$/;

O trecho (9\d{4}|\d{4}) diz: “ou um 9 seguido de 4 dígitos (celular), ou 4 dígitos (fixo)”. Repare como o grupo com | resolve em uma expressão o que levaria vários if.

Aceitando vários formatos de digitação

Na vida real, o usuário digita 11987878787, (11)98787-8787 ou +55 11 98787-8787. Em vez de criar uma regex gigante, uma abordagem mais robusta é normalizar primeiro e validar depois:

function validarTelefone(valor) {
  // remove tudo que não for dígito
  const digitos = valor.replace(/\D/g, "");

  // tira o código do país, se vier
  const semPais = digitos.startsWith("55") && digitos.length > 11
    ? digitos.slice(2)
    : digitos;

  // 10 dígitos (fixo) ou 11 dígitos (celular começando com 9)
  return /^\d{2}(9\d{8}|\d{8})$/.test(semPais);
}

validarTelefone("(11) 98787-8787");   // true
validarTelefone("+55 11 98787-8787"); // true
validarTelefone("11 4747-4747");      // true

Esse padrão (limpar a string e só então validar) deixa a regex curta, legível e muito mais fácil de manter. O mesmo vale para CEP, CPF e qualquer dado com máscara.

Como validar e-mail com regex (e por que não existe regex perfeita)

Validar e-mail é o caso de uso mais popular de regex, e também o mais mal resolvido. A especificação oficial de endereços de e-mail é tão permissiva que existem regexes de centenas de caracteres tentando cobrir todos os casos, e mesmo assim falham em algum.

Na prática, o que você quer na maioria dos formulários é filtrar erros de digitação óbvios, não provar que o endereço existe. Para isso, uma regex simples resolve:

const email = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;

email.test("contato@empresa.com.br"); // true
email.test("contato@empresa");        // false (sem domínio completo)
email.test("contato empresa.com");    // false (sem arroba)
email.test("@empresa.com");           // false (sem parte local)

Lendo a expressão: uma ou mais ocorrências de “qualquer coisa que não seja espaço nem arroba” ([^\s@]+), um arroba, mais uma sequência igual, um ponto literal (\.) e outra sequência final. Ela é permissiva de propósito: prefere deixar passar um e-mail estranho a barrar um legítimo.

O que essa regex NÃO garante

Ela aceita a@b.c, que tecnicamente tem o formato certo, mas provavelmente não é um e-mail real. E não faz nada contra joao@gmial.com, um erro de digitação que passa na validação sem problema nenhum.

Por isso a validação de e-mail tem duas camadas:

CamadaO que resolve
Regex no front-endBarra erros de formato óbvios na hora, sem esperar o servidor
Confirmação por e-mailProva que o endereço existe e que a pessoa tem acesso a ele

Se o e-mail é essencial (cadastro, recuperação de senha, envio de proposta), a confirmação por mensagem é a única validação de verdade. A regex é só um filtro de conveniência.

E o type=“email” do HTML?

O próprio navegador já valida e-mail quando você usa o tipo correto no campo:

<input type="email" name="email" required>

A especificação do HTML define uma regra própria para esse tipo de campo, mais permissiva que a maioria das regexes caseiras. Para telefone, o atributo pattern aceita uma regex diretamente:

<input
  type="tel"
  name="telefone"
  pattern="\(\d{2}\) \d{4,5}-\d{4}"
  title="Use o formato (11) 98787-8787"
  required
>

Dois detalhes importantes sobre o pattern: o navegador já trata a regex como se tivesse ^ e $ em volta, então você não precisa colocá-las; e o title é o texto que aparece como dica quando a validação falha.

E um aviso que vale ouro: toda validação no front-end pode ser contornada. Quem quiser enviar dados inválidos para o seu servidor consegue, desligando o JavaScript ou chamando a API diretamente. A validação do navegador melhora a experiência; a validação do servidor protege o sistema. Você precisa das duas.

Regex em JavaScript: os métodos que você vai usar

Em JavaScript, existem duas formas de criar uma regex: o literal, entre barras, e o construtor RegExp, útil quando o padrão vem de uma variável.

const literal = /\d+/g;
const construtor = new RegExp("\\d+", "g"); // atenção: a barra precisa ser dobrada na string

Os métodos principais:

MétodoO que faz
regex.test(texto)Retorna true ou false: o texto bate com o padrão?
texto.match(regex)Retorna os trechos encontrados (ou null)
texto.matchAll(regex)Retorna todas as ocorrências com detalhes de grupos (exige a flag g)
texto.replace(regex, novo)Troca o primeiro trecho encontrado (ou todos, com a flag g)
texto.replaceAll(regex, novo)Troca todas as ocorrências (exige a flag g em regex)
regex.exec(texto)Retorna o próximo match com detalhes, útil em loops

Exemplos práticos:

// Extrair todos os valores em reais de um texto
const texto = "Plano básico R$ 49,90 e plano pro R$ 129,00";
texto.match(/R\$ \d+,\d{2}/g);
// ["R$ 49,90", "R$ 129,00"]

// Remover espaços duplicados
"muito    espaço   aqui".replace(/\s+/g, " ");
// "muito espaço aqui"

// Deixar só dígitos (útil para CEP e CPF)
"01310-100".replace(/\D/g, "");
// "01310100"

Grupos de captura e grupos nomeados

Os parênteses não servem só para agrupar: eles também capturam o trecho que casou, para você reaproveitar. Com grupos nomeados, o código fica legível:

const data = /(?<dia>\d{2})\/(?<mes>\d{2})\/(?<ano>\d{4})/;
const resultado = "23/10/2026".match(data);

resultado.groups.dia; // "23"
resultado.groups.mes; // "10"
resultado.groups.ano; // "2026"

Você também pode usar os grupos na substituição, por exemplo para trocar o formato de uma data de 23/10/2026 para 2026-10-23:

"23/10/2026".replace(
  /(?<dia>\d{2})\/(?<mes>\d{2})\/(?<ano>\d{4})/,
  "$<ano>-$<mes>-$<dia>"
);
// "2026-10-23"

Lookahead: conferir sem consumir

O lookahead verifica se algo vem à frente, sem incluir isso no resultado. Um uso clássico é validar senha com regras múltiplas:

// Pelo menos 8 caracteres, uma letra maiúscula e um número
const senha = /^(?=.*[A-Z])(?=.*\d).{8,}$/;

senha.test("minhasenha1");  // false (sem maiúscula)
senha.test("MinhaSenha1");  // true

Cada (?=...) é uma condição que precisa ser verdadeira naquela posição. Vale usar com moderação: regex de senha com muitas regras fica difícil de ler e de manter.

Os erros mais comuns (e como evitá-los)

1. Esquecer as âncoras ^ e $

Sem âncoras, a regex só precisa encontrar o padrão em algum ponto da string. Compare:

/\d{4}-\d{4}/.test("abc 1234-5678 xyz");   // true (casou no meio)
/^\d{4}-\d{4}$/.test("abc 1234-5678 xyz"); // false (a string inteira precisa bater)

Para validar um campo, quase sempre você quer a string inteira batendo com o padrão. Isso significa usar ^ no começo e $ no fim.

2. Esquecer o escape

/R$ 10.00/ não faz o que parece: o $ vira âncora de fim de texto e o . casa com qualquer caractere. O correto é /R\$ 10\.00/.

3. Regex gulosa (greedy) demais

Por padrão, os quantificadores * e + são gulosos: pegam o máximo possível. Isso estraga a extração de trechos entre delimitadores:

const html = "<b>um</b> e <b>dois</b>";

html.match(/<b>.*<\/b>/);
// ["<b>um</b> e <b>dois</b>"] (pegou tudo, de um jeito que provavelmente você não queria)

html.match(/<b>.*?<\/b>/g);
// ["<b>um</b>", "<b>dois</b>"] (o ? depois do * torna a busca "preguiçosa")

4. Acentos e Unicode

\w e [a-z] não incluem letras acentuadas. Uma validação de nome com /^[a-zA-Z ]+$/ rejeita “João”, “Conceição” e “André”. Para nomes, use a flag u com propriedades Unicode:

/^[\p{L} ]+$/u.test("Conceição"); // true

O \p{L} significa “qualquer letra de qualquer idioma”. E, na dúvida, para nomes, não valide: quase sempre uma regex de nome rejeita alguém legítimo.

5. ReDoS: a regex que derruba o servidor

Esse é o erro menos conhecido e o mais perigoso. Algumas regexes, quando recebem uma entrada específica, entram em backtracking catastrófico: o interpretador testa um número exponencial de combinações e trava a aplicação.

O exemplo clássico:

const perigosa = /^(a+)+$/;

// Essa entrada termina com um caractere que não casa
perigosa.test("aaaaaaaaaaaaaaaaaaaaaaaaaaaa!");
// pode levar segundos, minutos ou travar o processo

O problema está nos quantificadores aninhados ((a+)+): para cada letra, o motor tenta todas as formas possíveis de dividir a sequência entre o grupo interno e o externo. Cada letra extra dobra o tempo de execução.

Se essa regex roda no servidor (em uma API em Node.js, por exemplo) e recebe dados de qualquer usuário, um atacante só precisa enviar uma string malcriada para travar o processo e derrubar o serviço. Isso é um ataque conhecido como ReDoS (Regular Expression Denial of Service) e entra na lista de preocupações de segurança de qualquer aplicação séria.

Como se proteger:

  • Evite quantificadores aninhados como (a+)+, (a*)* ou (a|aa)+.
  • Limite o tamanho da entrada antes de passar pela regex (por exemplo, rejeitar e-mails com mais de 254 caracteres).
  • Prefira regexes simples e específicas a regexes gigantes que tentam cobrir todos os casos.
  • Teste com entradas longas e inválidas, não só com os casos felizes.
  • Em código crítico, considere bibliotecas de regex com garantia de tempo linear.

Como testar e depurar regex

Nunca escreva uma regex complexa “às cegas”. Use um testador que mostre, em tempo real, o que está casando e explique cada pedaço:

  • Regex101: explica a expressão símbolo por símbolo, suporta JavaScript, PHP, Python e outros sabores, e mostra os grupos capturados.
  • RegExr: visual e interativo, com biblioteca de exemplos e referência de sintaxe embutida.
  • Console do navegador: para o teste final, rode a regex no mesmo ambiente em que ela vai viver. Alguns recursos (como grupos nomeados e \p{L}) variam entre navegadores e versões do Node.

Uma boa prática: guarde junto com a regex uma lista de casos de teste (válidos e inválidos). Quando alguém mexer na expressão daqui a seis meses, você tem como saber se quebrou alguma coisa.

const casos = [
  ["(11) 98787-8787", true],
  ["(11) 4747-4747", true],
  ["11987878787", false],
  ["(11) 9878-78787", false],
];

casos.forEach(([entrada, esperado]) => {
  const resultado = /^\(\d{2}\) (9\d{4}|\d{4})-\d{4}$/.test(entrada);
  console.assert(resultado === esperado, `Falhou: ${entrada}`);
});

Quando NÃO usar regex

Regex é uma ferramenta poderosa, e justamente por isso é tentador usá-la para tudo. Alguns casos em que ela é a escolha errada:

Interpretar HTML. Marcação aninhada e com atributos variados não é um formato “regular” no sentido técnico, e uma regex que funciona em 95% dos casos vai quebrar nos outros 5%. Para ler HTML, use um parser de verdade (DOMParser no navegador, Cheerio ou similares no servidor).

Validar CPF e CNPJ por completo. A regex consegue conferir o formato (/^\d{3}\.\d{3}\.\d{3}-\d{2}$/), mas a validade real depende dos dígitos verificadores, que exigem um cálculo. O formato é regex; a validade é função.

Validar datas. /^\d{2}\/\d{2}\/\d{4}$/ aceita “99/99/9999”. Para saber se a data existe, converta com Date (ou uma biblioteca) e confira o resultado.

Ler JSON ou XML. Esses formatos têm parsers prontos e confiáveis. Usar JSON.parse é mais simples, mais rápido e à prova de surpresas.

Quando uma busca simples resolve. Se você só quer saber se um texto contém uma palavra, texto.includes("palavra") é mais legível que qualquer regex.

A regra de bolso: se a regex está passando de uns 60 caracteres e ninguém do time consegue explicar o que ela faz sem abrir o Regex101, é hora de quebrar o problema em etapas menores.

Por que isso importa para o negócio: formulário que valida errado perde lead

Regex parece um assunto restrito a quem programa, mas o impacto aparece direto nos números do negócio. Um formulário de contato que rejeita telefones com formatos comuns, bloqueia e-mails de domínios mais novos (.dev.br, .com.br, .app) ou exibe uma mensagem de erro confusa faz o visitante desistir sem avisar ninguém. Para a empresa, isso é um lead que simplesmente nunca existiu no relatório.

Os sintomas mais comuns:

  • Telefone com máscara obrigatória que não aceita colar um número já formatado.
  • Validação de e-mail antiga que rejeita domínios com extensões longas.
  • Campo de nome que recusa acentos e apóstrofos.
  • Erro genérico (“campo inválido”) sem dizer qual formato é esperado.

Cada um desses pontos reduz a taxa de conversão do site de forma silenciosa. Ao revisar um formulário, vale testar com os dados reais que os clientes digitam, não só com os exemplos “bonitinhos” do desenvolvimento.

Checklist rápido antes de colocar uma regex em produção

  • A regex tem ^ e $ quando o objetivo é validar o campo inteiro?
  • Todos os caracteres especiais (., (, ), $, ?) estão com escape quando devem ser literais?
  • Ela aceita acentos e caracteres Unicode nos campos de texto livre?
  • Existe algum quantificador aninhado que possa causar ReDoS?
  • O tamanho máximo da entrada é limitado antes de testar a regex?
  • A validação também existe no servidor, e não só no navegador?
  • Há uma lista de casos de teste (válidos e inválidos) guardada junto do código?
  • A mensagem de erro diz ao usuário qual formato é esperado?

Perguntas frequentes sobre regex

O que é regex?

É uma sequência de caracteres que descreve um padrão de texto. Linguagens de programação usam esse padrão para validar, buscar, extrair ou substituir trechos de uma string. O nome vem do inglês regular expression, ou expressão regular em português.

Regex e expressão regular são a mesma coisa?

Sim. “Regex” é a abreviação em inglês (regular expression) e “expressão regular” ou “ER” é o termo em português. Os dois nomes se referem exatamente à mesma coisa.

Como validar e-mail com regex?

Uma regex simples como /^[^\s@]+@[^\s@]+\.[^\s@]+$/ filtra erros de formato óbvios, como falta de arroba ou de domínio. Ela não prova que o e-mail existe: para isso, é preciso enviar uma mensagem de confirmação. Em formulários HTML, o campo type="email" já faz uma validação básica no navegador.

Como validar telefone com regex?

Para o formato brasileiro com máscara, /^\(\d{2}\) \d{4,5}-\d{4}$/ funciona. Uma abordagem mais flexível é remover tudo que não for dígito (valor.replace(/\D/g, "")) e validar a quantidade de dígitos: 10 para fixo e 11 para celular.

O que significam ^ e $ em uma regex?

O ^ marca o início do texto e o $ marca o fim. Usados juntos, obrigam que a string inteira corresponda ao padrão. Sem eles, a regex casa se encontrar o padrão em qualquer ponto do texto, o que costuma gerar validações falsas.

Qual a diferença entre test(), match() e replace() em JavaScript?

O test() só responde true ou false. O match() devolve os trechos encontrados. O replace() troca os trechos encontrados por outro texto. Para validar um campo, test() é o mais direto.

O que é ReDoS?

É um ataque (ou um bug) em que uma regex mal escrita leva tempo exponencial para processar certas entradas, travando a aplicação. Acontece principalmente com quantificadores aninhados, como (a+)+. A defesa é evitar esses padrões e limitar o tamanho da entrada antes de testá-la.

Preciso validar no servidor se já valido no navegador?

Sim. A validação no navegador melhora a experiência do usuário, mas pode ser contornada por qualquer pessoa que envie dados diretamente para a sua API. O servidor precisa validar tudo de novo.

Regex funciona igual em todas as linguagens?

A base é a mesma, mas existem diferenças de sintaxe e de recursos entre linguagens (grupos nomeados, lookbehind, propriedades Unicode). Sempre teste a regex no ambiente em que ela vai rodar, e escolha o “sabor” correto em ferramentas como o Regex101.

Qual a melhor forma de aprender regex?

Começar por padrões pequenos e específicos (telefone, CEP, data) e testar cada um em uma ferramenta visual. Evite começar por regexes complexas. Entender bem os blocos básicos (classes, quantificadores e âncoras) resolve a grande maioria dos casos.

Conclusão

Regex não é um bicho de sete cabeças: é um conjunto pequeno de blocos (caracteres, classes, quantificadores, âncoras e grupos) que, combinados, descrevem praticamente qualquer formato de texto. O segredo está em escrever expressões pequenas, legíveis e testadas, em saber o limite da ferramenta (e-mail se confirma por mensagem, CPF se valida por cálculo, HTML se lê com um parser) e em lembrar que uma regex descuidada pode tanto rejeitar um cliente legítimo quanto derrubar um servidor.

Se o seu site tem formulários que perdem contatos por validar errado, ou se você precisa de integrações, automações e formulários que conversem corretamente com CRM, e-mail e pagamento, esse é o tipo de detalhe que faz diferença no resultado. É o trabalho que faço como desenvolvedor freelancer, do formulário até a integração entre sistemas, com validação feita do jeito certo, no front-end e no servidor.

Quer entender melhor como o front-end de um site funciona por trás? Veja também o que é desenvolvimento web e por que evitar void(0) no seu código é outra daquelas boas práticas que parecem pequenas, mas pesam no resultado.

Referências

Leia também

Artigos Relacionados