
Pour valider le format d’une adresse e-mail, testez-la avec la regex ci-dessous (test() en JavaScript, preg_match() en PHP). Elle accepte prenom.nom+tag@sous.domaine.fr et refuse les points doublés, les espaces et les domaines sans extension. Elle ne garantit pas que la boîte existe : pour cela, il faut envoyer un message ou interroger le domaine.
Une regex email fait une seule chose : vérifier qu’une chaîne a la forme d’une adresse électronique. C’est peu, et c’est indispensable dans tout formulaire d’inscription, de contact ou de commande. Voici l’expression régulière que j’utilise depuis des années, avec ses versions JavaScript, PHP et HTML, et surtout le détail de ce qu’elle accepte et refuse.
La regex email prête à copier
^(([^<>()[\]\\.,;:\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,}))$Lue de gauche à droite :
^et$: la chaîne entière doit être une adresse, pas seulement en contenir une.[^<>()[\]\\.,;:\s@"]+: la partie locale (avant le@) est faite de caractères autorisés, sans espace ni ponctuation réservée. Les groupes séparés par un point (prenom.nom) sont acceptés, un point doublé ne l’est pas.|(".+"): l’alternative entre guillemets, prévue par la norme, autorise"jean dupont"@example.com.@, puis le domaine : soit une adresse IP entre crochets ([192.168.1.1]), soit des libellés alphanumériques séparés par des points et terminés par une extension d’au moins deux lettres.
Cette règle a remplacé en octobre 2022 une version dont les antislashs avaient disparu lors de la migration du site en 2021. Si vous aviez copié l’ancienne, remplacez-la : elle laissait passer des adresses malformées.
Ce que la regex vérifie, et ce qu’elle ne vérifie pas
Le tableau ci-dessous est le résultat réel de test() sous Node.js 22 et de preg_match() sous PHP 8.5 sur seize adresses. Les deux moteurs donnent le même verdict.
| Adresse testée | Résultat | Pourquoi |
|---|---|---|
jean.dupont@example.com | acceptée | le cas standard |
jean+news@sub.example.co.uk | acceptée | le + et les sous-domaines sont légitimes |
JEAN@EXAMPLE.COM | acceptée | le drapeau i ignore la casse |
"jean dupont"@example.com | acceptée | partie locale entre guillemets, prévue par la RFC 5322 |
jean@[192.168.1.1] | acceptée | domaine sous forme d’adresse IP |
jéan@exemple.fr | acceptée | la partie locale accepte les caractères non ASCII |
jean@localhost | refusée | pas d’extension après le point |
jean@example | refusée | même raison |
jean..dupont@example.com | refusée | point doublé dans la partie locale |
jean@exa_mple.com | refusée | le tiret bas n’est pas autorisé dans un nom de domaine |
jean@exemple.café | refusée | extension accentuée : voir les limites |
a@b.c | refusée | extension d’une seule lettre |
@example.com | refusée | partie locale vide |
jean@.com | refusée | domaine vide avant l’extension |
jean dupont@example.com | refusée | espace |
jean@example.com (espace final) | refusée | l’ancre $ refuse tout caractère après l’extension : pensez à trim() |
Ce qu’aucune regex ne peut vérifier : que le domaine existe, qu’il accepte du courrier et que la boîte est active. personne@gmail.com a une forme parfaite et ne reçoit probablement rien.
Version JavaScript
La fonction prend l’adresse en paramètre, retire les espaces autour, et renvoie true ou false. Le drapeau i rend la comparaison insensible à la casse.
/**
* Vérifie le format d'une adresse e-mail.
* @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, grâce au trimDans un formulaire
Le cas d’usage typique : bloquer l’envoi et afficher un message tant que l’adresse est mal formée.
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();
}
});Le champ d’erreur est un élément visible à côté du champ, pas une boîte alert() : l’utilisateur garde le contexte et peut corriger immédiatement.
Version PHP
La validation côté client est une aide à la saisie, pas une sécurité. Le serveur doit refaire le contrôle : le JavaScript peut être désactivé, et un formulaire peut être appelé directement. La même regex fonctionne avec preg_match(), aux guillemets près.
/**
* Vérifie le format d'une adresse e-mail.
*/
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)Notez le double échappement des antislashs dans la chaîne PHP entre apostrophes : \\\\ dans le code produit \\ dans le motif, qui signifie « un antislash littéral » pour le moteur de regex.
Regex ou filter_var ?
PHP fournit filter_var($email, FILTER_VALIDATE_EMAIL), qui implémente la norme de plus près. Les deux approches sont valables, elles ne tranchent pas tout à fait de la même façon :
| Adresse | Regex de l’article | filter_var |
|---|---|---|
a@b.c | refusée | acceptée |
"jean dupont"@example.com | acceptée | refusée |
jéan@exemple.fr | acceptée | refusée sans FILTER_FLAG_EMAIL_UNICODE, acceptée avec |
jean+news@sub.example.co.uk | acceptée | acceptée |
Pour un formulaire public, filter_var avec le drapeau Unicode est un bon choix par défaut, la regex reste utile quand vous voulez la même règle exactement en JavaScript et en PHP.
Version HTML
Le navigateur sait déjà valider un e-mail : type="email" refuse l’envoi du formulaire si le format est mauvais, et affiche son propre message. L’attribut pattern permet d’ajouter une règle plus stricte, celle de l’article par exemple.
<label for="email">Adresse e-mail</label>
<input
id="email"
name="email"
type="email"
required
autocomplete="email"
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,}))"
title="Une adresse de la forme prenom.nom@domaine.fr"
>Quatre détails : pattern est implicitement ancré, il ne faut donc pas écrire ^ et $, les guillemets du motif s’écrivent " pour ne pas fermer l’attribut, les navigateurs compilent pattern avec le drapeau v, qui exige d’échapper les parenthèses et les crochets à l’intérieur d’une classe de caractères (\(\)\[\]), faute de quoi le motif est ignoré en silence, et la validation HTML se contourne en une ligne dans la console. Elle améliore l’expérience utilisateur, elle ne remplace pas le contrôle serveur.
Cas limites à connaître
- Le
+:jean+boutique@gmail.comest une adresse valide que beaucoup de gens utilisent pour trier leur courrier. Une regex qui la refuse perd des inscriptions. - Les extensions longues :
.photography,.paris,.technologyexistent. C’est pourquoi le motif se termine par[a-zA-Z]{2,}et non par une liste fermée d’extensions. - Les accents : la partie locale les accepte, le domaine ne les accepte pas dans cette regex (les domaines internationalisés s’écrivent en réalité en Punycode,
xn--…, avant d’être résolus). - Les espaces autour : un copier-coller depuis un tableur ajoute souvent un espace final. Appelez
trim()avant le test, comme le font les deux fonctions ci-dessus. - La casse : le domaine est insensible à la casse, la partie locale l’est en théorie sensible, mais aucun fournisseur grand public ne fait la différence. Comparer en minuscules est le comportement attendu.
Tester la regex avant de la déployer
Un jeu d’essai vaut mieux qu’une relecture. En JavaScript, une boucle sur des adresses connues suffit :
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}`);
}Pour explorer un motif, regex101 colore chaque groupe et explique ce qu’il capture. Choisissez la saveur ECMAScript pour JavaScript et PCRE2 pour PHP : les deux acceptent cette regex sans modification.
Aller plus loin : vérifier que l’adresse existe
Quand la forme est bonne, trois contrôles supplémentaires réduisent les fausses adresses, du moins coûteux au plus fiable :
- Refuser les domaines jetables (adresses temporaires créées pour contourner une inscription) à partir d’une liste maintenue.
- Interroger les enregistrements MX du domaine côté serveur : un domaine sans serveur de courrier ne recevra jamais de message. En PHP,
checkdnsrr($domaine, 'MX')répond en une ligne. - Envoyer un e-mail de confirmation avec un lien à cliquer. C’est la seule preuve qu’une personne lit cette boîte, et c’est ce que font tous les services sérieux.
Les deux premiers contrôles sont ceux que j’applique sur les formulaires publics de Foliade, le troisième reste la référence dès qu’un compte est créé.
Les autres expressions régulières de validation
Même approche, autres formats : valider une date, une adresse IP, un code postal ou un mot de passe.
Erreurs fréquentes
test() accepte « bonjour jean@example.com merci » parce qu’une adresse valide se trouve quelque part dans la chaîne. La fonction de cet article ancre la regex aux deux extrémités.jean+newsletter@…) ou les nouvelles extensions longues comme .photography. Testez toujours avec des adresses réelles avant de durcir la règle.

