Regex email: validar una dirección de correo en JavaScript y PHP

Una regex de email lista para copiar, probada con casos reales, con la función JavaScript, el equivalente en PHP y el atributo HTML: qué comprueba, qué deja pasar y cómo ir más lejos.

Expresión regular para validar un email en JavaScript y PHP
Respuesta rápida

Para validar el formato de una dirección de correo, pruébala con la regex de abajo (test() en JavaScript, preg_match() en PHP). Acepta prenom.nom+tag@sous.domaine.fr y rechaza los puntos duplicados, los espacios y los dominios sin extensión. No garantiza que el buzón exista: para eso hay que enviar un mensaje o consultar el dominio.

Una regex de email hace una sola cosa: comprobar que una cadena tiene forma de dirección de correo. Es poco, y es imprescindible en cualquier formulario de registro, de contacto o de pedido. Esta es la expresión regular que uso desde hace años, con sus versiones en JavaScript, PHP y HTML y, sobre todo, el detalle de lo que acepta y de lo que rechaza.

La regex de email lista para copiar

javascript
^(([^<>()[\]\\.,;:\s@"]+(\.[^<>()[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$

Leída de izquierda a derecha:

  • ^ y $: la cadena entera debe ser una dirección, no basta con que contenga una.
  • [^<>()[\]\\.,;:\s@"]+: la parte local (antes de la @) se compone de caracteres permitidos, sin espacios ni signos de puntuación reservados. Se aceptan los grupos separados por un punto (prenom.nom), pero no un punto duplicado.
  • |(".+"): la alternativa entre comillas, prevista por la norma, permite "jean dupont"@example.com.
  • @ y después el dominio: o bien una dirección IP entre corchetes ([192.168.1.1]), o bien etiquetas alfanuméricas separadas por puntos y terminadas en una extensión de dos letras como mínimo.
Historial

Esta regla sustituyó en octubre de 2022 a una versión a la que le faltaban las barras invertidas, perdidas durante la migración del sitio de 2021. Si copiaste la antigua, cámbiala: dejaba pasar direcciones mal formadas.

Lo que la regex comprueba y lo que no

La tabla siguiente recoge el resultado real de test() en Node.js 22 y de preg_match() en PHP 8.5 sobre dieciséis direcciones. Los dos motores dan el mismo veredicto.

Dirección probada Resultado Por qué
jean.dupont@example.com aceptada el caso estándar
jean+news@sub.example.co.uk aceptada el + y los subdominios son legítimos
JEAN@EXAMPLE.COM aceptada el modificador i ignora mayúsculas y minúsculas
"jean dupont"@example.com aceptada parte local entre comillas, prevista por la RFC 5322
jean@[192.168.1.1] aceptada dominio en forma de dirección IP
jéan@exemple.fr aceptada la parte local admite caracteres no ASCII
jean@localhost rechazada no hay extensión después del punto
jean@example rechazada el mismo motivo
jean..dupont@example.com rechazada punto duplicado en la parte local
jean@exa_mple.com rechazada el guion bajo no está permitido en un nombre de dominio
jean@exemple.café rechazada extensión con acento: consulta los límites
a@b.c rechazada extensión de una sola letra
@example.com rechazada parte local vacía
jean@.com rechazada dominio vacío antes de la extensión
jean dupont@example.com rechazada espacio
jean@example.com (espacio final) rechazada el ancla $ rechaza cualquier carácter después de la extensión: acuérdate de trim()

Lo que ninguna regex puede comprobar: que el dominio exista, que acepte correo y que el buzón esté activo. personne@gmail.com tiene una forma perfecta y probablemente no recibe nada.

Versión JavaScript

La función recibe la dirección como parámetro, quita los espacios de alrededor y devuelve true o false. El modificador i hace que la comparación no distinga mayúsculas de minúsculas.

validateEmail.js
/**
 * Comprueba el formato de una dirección de correo.
 * @param {string} email
 * @returns {boolean}
 */
function validateEmail(email) {
  const emailReg = /^(([^<>()[\]\\.,;:\s@"]+(\.[^<>()[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/i;
  return emailReg.test(String(email).trim());
}

console.log(validateEmail('jean.dupont@example.com')); // true
console.log(validateEmail('jean..dupont@example.com')); // false
console.log(validateEmail(' jean@example.com '));      // true, gracias al trim

En un formulario

El caso de uso típico: bloquear el envío y mostrar un mensaje mientras la dirección esté mal formada.

formulaire.js
const form = document.querySelector('#inscription');
const champ = document.querySelector('#email');
const erreur = document.querySelector('#email-erreur');

form.addEventListener('submit', (event) => {
  if (!validateEmail(champ.value)) {
    event.preventDefault();
    erreur.textContent = 'Adresse e-mail invalide';
    champ.setAttribute('aria-invalid', 'true');
    champ.focus();
  }
});

El mensaje de error es un elemento visible junto al campo, no un alert(): el usuario no pierde el contexto y puede corregir al momento.

Versión PHP

La validación en el cliente es una ayuda para escribir, no una medida de seguridad. El servidor tiene que repetir la comprobación: el JavaScript se puede desactivar y un formulario se puede llamar directamente. La misma regex funciona con preg_match(), salvo por las comillas.

isValidEmail.php
/**
 * Comprueba el formato de una dirección de correo.
 */
function isValidEmail(string $email): bool
{
    $pattern = '/^(([^<>()[\]\\\\.,;:\s@"]+(\.[^<>()[\]\\\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/i';

    return preg_match($pattern, trim($email)) === 1;
}

var_dump(isValidEmail('jean.dupont@example.com')); // bool(true)
var_dump(isValidEmail('jean@localhost'));          // bool(false)

Fíjate en el doble escape de las barras invertidas dentro de la cadena PHP entre comillas simples: \\\\ en el código produce \\ en el patrón, que significa «una barra invertida literal» para el motor de regex.

¿Regex o filter_var?

PHP incluye filter_var($email, FILTER_VALIDATE_EMAIL), que se ciñe más a la norma. Los dos enfoques son válidos; no deciden exactamente igual:

Dirección Regex del artículo filter_var
a@b.c rechazada aceptada
"jean dupont"@example.com aceptada rechazada
jéan@exemple.fr aceptada rechazada sin FILTER_FLAG_EMAIL_UNICODE, aceptada con él
jean+news@sub.example.co.uk aceptada aceptada

Para un formulario público, filter_var con el flag Unicode es una buena opción por defecto; la regex sigue siendo útil cuando quieres exactamente la misma regla en JavaScript y en PHP.

Versión HTML

El navegador ya sabe validar un correo: type="email" impide enviar el formulario si el formato es incorrecto y muestra su propio mensaje. El atributo pattern permite añadir una regla más estricta, la del artículo por ejemplo.

formulaire.html
<label for="email">Adresse e-mail</label>
<input
  id="email"
  name="email"
  type="email"
  required
  autocomplete="email"
  pattern="(([^<>\(\)\[\]\\.,;:\s@&quot;]+(\.[^<>\(\)\[\]\\.,;:\s@&quot;]+)*)|(&quot;.+&quot;))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))"
  title="Une adresse de la forme prenom.nom@domaine.fr"
>

Cuatro detalles: pattern lleva anclas implícitas, así que no hay que escribir ^ ni $; las comillas del patrón se escriben &quot; para no cerrar el atributo; los navegadores compilan pattern con el flag v, que obliga a escapar los paréntesis y los corchetes dentro de una clase de caracteres (\(\)\[\]), y si no el patrón se ignora en silencio; y la validación HTML se salta con una línea en la consola. Mejora la experiencia de usuario, pero no sustituye a la comprobación en el servidor.

Casos límite que conviene conocer

  • El +: jean+boutique@gmail.com es una dirección válida que mucha gente usa para clasificar su correo. Una regex que la rechaza pierde registros.
  • Las extensiones largas: .photography, .paris y .technology existen. Por eso el patrón termina en [a-zA-Z]{2,} y no en una lista cerrada de extensiones.
  • Los acentos: la parte local los admite; el dominio no los admite en esta regex (los dominios internacionalizados se escriben en realidad en Punycode, xn--…, antes de resolverse).
  • Los espacios alrededor: un copiar y pegar desde una hoja de cálculo suele añadir un espacio al final. Llama a trim() antes de la comprobación, como hacen las dos funciones anteriores.
  • Mayúsculas y minúsculas: el dominio no distingue entre unas y otras; la parte local sí, en teoría, pero ningún proveedor de uso general hace esa distinción. Comparar en minúsculas es el comportamiento esperado.

Probar la regex antes de desplegarla

Un juego de pruebas vale más que una relectura. En JavaScript basta con un bucle sobre direcciones conocidas:

test.js
const cas = {
  'jean.dupont@example.com': true,
  'jean+news@sub.example.co.uk': true,
  'jean@localhost': false,
  'jean..dupont@example.com': false,
  'jean dupont@example.com': false,
};

for (const [email, attendu] of Object.entries(cas)) {
  const ok = validateEmail(email) === attendu;
  console.log(`${ok ? 'OK ' : 'KO '} ${email}`);
}

Para explorar un patrón, regex101 colorea cada grupo y explica qué captura. Elige el sabor ECMAScript para JavaScript y PCRE2 para PHP: los dos aceptan esta regex sin cambios.

Ir más lejos: comprobar que la dirección existe

Cuando el formato es correcto, tres comprobaciones adicionales reducen las direcciones falsas, de la más barata a la más fiable:

  1. Rechazar los dominios desechables (direcciones temporales creadas para saltarse un registro) a partir de una lista mantenida.
  2. Consultar los registros MX del dominio en el servidor: un dominio sin servidor de correo no recibirá nunca un mensaje. En PHP, checkdnsrr($domaine, 'MX') responde en una línea.
  3. Enviar un correo de confirmación con un enlace en el que hacer clic. Es la única prueba de que alguien lee ese buzón, y es lo que hacen todos los servicios serios.

Las dos primeras comprobaciones son las que aplico en los formularios públicos de Foliade; la tercera sigue siendo la referencia en cuanto se crea una cuenta.

Las demás expresiones regulares de validación

Mismo enfoque, otros formatos: validar una fecha, una dirección IP, un código postal o una contraseña.

Errores frecuentes

Olvidar las anclas ^ y $ Sin ellas, test() acepta «hola jean@example.com gracias» porque hay una dirección válida en algún punto de la cadena. La función de este artículo ancla la regex por los dos extremos.
Validar solo en el cliente El JavaScript se desactiva y se esquiva. La misma comprobación debe existir en el servidor, aquí en PHP, antes de guardar o de enviar nada.
Creer que una dirección válida existe La regex comprueba una forma, no un buzón. «nimportequoi@gmail.com» pasa. Solo un correo de confirmación demuestra que la dirección recibe mensajes.
Rechazar direcciones legítimas Las regex demasiado estrictas rechazan el «+» (jean+newsletter@…) o las nuevas extensiones largas como .photography. Prueba siempre con direcciones reales antes de endurecer la regla.

JavaScriptPHP

Damien Flandrin Desarrollador web desde 2010, creador de Gekkode y de Email Impact. Cada artículo se prueba en un proyecto real antes de publicarse. Contacto
Newsletter

Las nuevas pruebas, tutoriales y proyectos, por correo.

Pruebas reproducibles, código versionado, resultados fechados. Nunca spam.