Regex for email addresses: what can and cannot be validated
No regex validates an email address, because the grammar permits quoted local parts, comments and address literals. A pattern can catch typos, and only a confirmation message proves the mailbox exists.
At a glance
- Built in preset
- Preset pattern
- [a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}
- Preset flags
- g and i
- Accepts
- an ordinary local part, a dotted domain and a top level domain of two or more letters
- Rejects
- quoted local parts, address literals such as user@[192.168.0.1], and bare hosts
- Does not check
- whether the domain resolves or the mailbox exists
In detail
The honest starting point is that the address grammar is far larger than the shape people picture. It permits a quoted local part with spaces inside it, comments in parentheses, and a domain given as a bracketed address literal. A pattern that accepts all of that would also accept a great deal of nonsense, and every well known short email regex is a compromise rather than an implementation of the specification.
The tester's preset is one of those compromises and it is worth knowing exactly where it sits. It requires at least one character from a conventional local part set, an at sign, a domain made of letters, digits, dots and hyphens, and a final dot followed by two or more letters. On the sample it ships with it finds both addresses and nothing else.
What it rejects is the interesting half. A quoted local part fails, an address literal fails because the square brackets are not in the domain class, and an internationalised address in a non-Latin script fails too. It also accepts things a mail server would not, including a local part with two consecutive dots, so it is neither a superset nor a subset of what is deliverable.
For a signup form that is fine, provided you know what job it is doing. The pattern is a typo filter: it catches a missing at sign and a domain with no dot, which is most of what users actually get wrong. The real validation is sending a message to the address and waiting for someone to click the link, and no amount of pattern work replaces that step.
Examples
const email = /[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}/gi;
"Contact us at support@example.com or sales@company.co.uk for help."
.match(email);
// ["support@example.com", "sales@company.co.uk"]const email = /[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}/i;
email.test('"john doe"@example.com'); // false, quoted local part
email.test("user@[192.168.0.1]"); // false, address literal
email.test("a..b@example.com"); // true, and a server may still refuse itThings that catch people out
- The preset has no anchors, so used with test it reports success on a string that merely contains an address. Wrap it in ^ and $ before treating it as a validator.
- A trailing full stop after the domain is left out of the match, which is right for finding addresses in prose and hides the fact that the input was malformed.
- Tightening the pattern to reject unusual addresses rejects real users. Plus addressing, long top level domains and non-Latin domains are all legitimate and all commonly broken by a stricter regex.
Try it: Regex Tester
Type a pattern and a test string, watch every match highlight as you go, read a plain English breakdown of each token, and check the numbered and named groups it captured.
Regex Tester
LOCAL · Browser-OnlyTest and debug regular expressions with live matching and highlights
Related Regex syntax
All regex syntax →Regex for URLs: finding links without overreaching
The built in URL preset needs a scheme and a dotted host, which rules out localhost and bare ports. It also swallows a full stop at the end of a path.
Regex for phone numbers, and why strict ones lose customers
Phone formatting varies by country and by habit. The built in US preset does not even match the parenthesised form in its own sample, which is the lesson.
[a-z] ranges: code point order and the A-z bug
Ranges inside a character class run by code point, which is why [A-z] quietly includes six punctuation characters and [a-Z] is a syntax error.
Regex for IPv4 addresses with real octet range checks
The naive four groups of digits pattern accepts 999.999.999.999. Here is the alternation that enforces 0 to 255, and the leading zero case it still allows.
Regex for UUIDs: matching the 8-4-4-4-12 layout
The preset matches the hexadecimal layout of any UUID. Pinning it to version 4 takes two extra character classes, one for the version and one for the variant.
Regex for hex colours: three, six and eight digit forms
The built in preset matches the three and six digit forms and rejects the four and eight digit ones with alpha. Here is the pattern that covers all four.
Tools for this
- Regex Tester Free online regex tester - test and debug regular expressions with live matching and highlights
- AI Regex Generator Free online AI regex generator - browse 35+ common regex patterns or generate custom ones with AI
- Regex Playground Regex playground with live match highlighting, named groups, find and replace, and shareable links