Quick answer
A practical email regex is ^[A-Za-z0-9_%+'-]+(?:\.[A-Za-z0-9_%+'-]+)*@(?:[A-Za-z0-9](?:[A-Za-z0-9-]{0,61}[A-Za-z0-9])?\.)+(?:[A-Za-z]{2,63}|xn--[A-Za-z0-9-]{1,59})$. It accepts addresses like jane.doe@example.com, user+tag@mail.example.co.uk and Punycode domains, and rejects missing @ signs, doubled dots, hyphen-edged domain labels and one-letter top-level domains. Use it for instant form feedback, then confirm the address by sending an email: no regex can tell you a mailbox exists.
- Tested examples
- Token-by-token breakdown
- Code in 5 languages
- Live tester
How to use the email address regex
- 1Copy the pattern with Copy pattern in the tester, or take a ready-made snippet for your language from the code section below.
- 2Check the sample lines in Test string: every line the email address regex accepts is highlighted, rejected lines stay plain.
- 3Paste your own values one per line to see which pass, and read Explanation for what each part of the pattern does.
- 4Open the Code tab for JavaScript, Python, Go, C#, PHP or Dart with the pattern translated and escaped.
- 5Validate again on the server: client-side checks give users quick feedback, not security.
How does the email address regex work?
Read it left to right. Each piece below matches one part of the input:
^Start of the string, so nothing can come before the address.[A-Za-z0-9_%+'-]+The first part of the local name: letters, digits and _ % + ' -. The + allows plus-addressing like jane+news.(?:\.[A-Za-z0-9_%+'-]+)*More dot-separated parts. Each dot must be followed by characters, so .jane, jane. and jane..doe all fail.@Exactly one at sign.(?:[A-Za-z0-9](?:[A-Za-z0-9-]{0,61}[A-Za-z0-9])?\.)+One or more domain labels, each 1–63 characters, starting and ending with a letter or digit, followed by a dot.(?:[A-Za-z]{2,63}|xn--[A-Za-z0-9-]{1,59})The top-level domain: 2 to 63 letters (com, io, museum), or an internationalized one in Punycode such as xn--p1ai (.рф).$End of the string.
Which strings does the email address regex match?
The whole input must match, from the first character to the last. Every row below is checked against the pattern by our automated test suite before each release.
| Input | Matches? | Why |
|---|---|---|
jane.doe@example.com | Yes | The everyday case: dotted local part, ordinary domain. |
user+newsletter@mail.example.co.uk | Yes | Plus-addressing and a multi-label domain. |
o'brien@example.ie | Yes | Apostrophes are legal and common in Irish and French names. |
first_last@example.museum | Yes | Long top-level domains are fine (up to 63 letters). |
1234567@numbers.org | Yes | An all-digit local part is valid. |
info@xn--bcher-kva.xn--p1ai | Yes | An internationalized domain and top-level domain, in Punycode. |
plainaddress | No | No @ sign. |
@example.com | No | Empty local part. |
jane..doe@example.com | No | Two dots in a row are not allowed in an unquoted local part. |
.jane@example.com | No | The local part cannot start with a dot. |
jane@example | No | No top-level domain. Intranet-only hosts are rejected on purpose. |
jane@-example.com | No | Domain labels cannot start with a hyphen. |
jane doe@example.com | No | Spaces are not allowed (outside rare quoted forms). |
jane@example.c | No | No real top-level domain has a single letter. |
Can a regex fully validate an email address?
No, and it should not try. The official grammar in RFC 5322 allows quoted local parts ("john smith"@example.com), comments in parentheses and IP-address literals (jane@[192.0.2.1]). A regex that accepts all of that is hundreds of characters long and still cannot tell you whether the mailbox exists.
This pattern takes the practical route: it accepts what real people type and rejects the typos that matter (a missing @, a doubled dot, a domain with no top-level part). Then:
- Trim the input and compare case-insensitively: the domain part is not case-sensitive.
- Send a confirmation link. That is the only check that proves the address works and belongs to the user.
- Do not block unusual but valid addresses at signup. A false rejection loses a real customer; a false acceptance only costs a bounced email.
What about international and unusual addresses?
Two cases deserve a decision rather than a default:
- Internationalized addresses such as
用户@例子.广告(RFC 6531) use non-ASCII letters. This pattern rejects them. If you serve those markets, replace the ASCII classes with[\p{L}\p{N}…]and add theuflag, or accept anylocal@domainand rely on the confirmation email. - Browser behavior:
<input type="email">uses the WHATWG pattern, which allowsjane@localhostandjane..doe@example.com. Keep your server-side check at least as strict as the form, or users will see different answers in different places.
Length matters too: an address can be at most 254 characters in practice, so add a length check before running the regex on untrusted input.
How do I use the email address regex in JavaScript, Python, Go, C# and PHP?
The pattern is translated for each engine rather than pasted as is: named groups, anchors, escapes, shorthand classes and flags are adjusted so each snippet behaves as closely to the JavaScript version as its engine allows. Differences that remain (Go's RE2 has no lookarounds or backreferences, for example) are called out under each one. The Code tab in the tester above also exports Dart, and updates as you edit the pattern.
JavaScript and TypeScript
const regex = /^[A-Za-z0-9_%+'-]+(?:\.[A-Za-z0-9_%+'-]+)*@(?:[A-Za-z0-9](?:[A-Za-z0-9-]{0,61}[A-Za-z0-9])?\.)+(?:[A-Za-z]{2,63}|xn--[A-Za-z0-9-]{1,59})$/;
function isValid(value) {
return regex.test(value);
}
console.log(isValid("jane.doe@example.com"));Python
import re
pattern = re.compile(r"^[A-Za-z0-9_%+'\-]+(?:\.[A-Za-z0-9_%+'\-]+)*@(?:[A-Za-z0-9](?:[A-Za-z0-9\-]{0,61}[A-Za-z0-9])?\.)+(?:[A-Za-z]{2,63}|xn--[A-Za-z0-9\-]{1,59})\Z")
def is_valid(value: str) -> bool:
return pattern.search(value) is not None
print(is_valid("jane.doe@example.com"))- JavaScript's $ only matches at the very end of the string. Python's $ also matches before a trailing newline, so the pattern uses \Z instead.
Go
package main
import (
"fmt"
"regexp"
)
func main() {
re := regexp.MustCompile(`^[A-Za-z0-9_%+'\-]+(?:\.[A-Za-z0-9_%+'\-]+)*@(?:[A-Za-z0-9](?:[A-Za-z0-9\-]{0,61}[A-Za-z0-9])?\.)+(?:[A-Za-z]{2,63}|xn--[A-Za-z0-9\-]{1,59})$`)
fmt.Println(re.MatchString("jane.doe@example.com"))
}C#
using System;
using System.Text.RegularExpressions;
// The timeout stops a pathological input from hanging the thread (ReDoS).
var regex = new Regex(@"^[A-Za-z0-9_%+'\-]+(?:\.[A-Za-z0-9_%+'\-]+)*@(?:[A-Za-z0-9](?:[A-Za-z0-9\-]{0,61}[A-Za-z0-9])?\.)+(?:[A-Za-z]{2,63}|xn--[A-Za-z0-9\-]{1,59})\z", RegexOptions.None, TimeSpan.FromSeconds(1));
Console.WriteLine(regex.IsMatch("jane.doe@example.com"));- JavaScript's $ only matches at the very end of the string. .NET's $ also matches before a trailing newline, so the pattern uses \z instead.
PHP
<?php
$pattern = '/^[A-Za-z0-9_%+\'\-]+(?:\.[A-Za-z0-9_%+\'\-]+)*@(?:[A-Za-z0-9](?:[A-Za-z0-9\-]{0,61}[A-Za-z0-9])?\.)+(?:[A-Za-z]{2,63}|xn--[A-Za-z0-9\-]{1,59})$/D';
function isValid(string $value, string $pattern): bool
{
return preg_match($pattern, $value) === 1;
}
var_dump(isValid('jane.doe@example.com', $pattern));- The D modifier makes $ match only at the very end, like JavaScript (PCRE's $ also matches before a trailing newline).
Which related regex patterns are useful?
- Regex for URL Validation: http and https links with a real domain, optional port, path, query and fragment.
- Regex for Domain Names: Hostnames with valid labels, a 253-character limit and Punycode support.
- Regex for Usernames: 3–16 characters, starts with a letter, single separators between letters and digits.
- Regex for Phone Numbers (E.164 Format): International numbers in E.164: a plus, a country code and up to 15 digits in total.
Browse all 25 patterns in the regex pattern library, or build your own in the regex tester.
Frequently asked questions
Last updated by the Playxoft engineering team.