Email regex: the simple, the practical and the strict
Email is the most requested regex pattern and the most misunderstood. The fully RFC 5322-compliant pattern is over 6,000 characters long, which is why every serious project picks a pragmatic middle ground. The rule of thumb: validate structure with regex, confirm existence with a confirmation email. Anything stricter than the patterns below rejects real addresses that are legal.
| Goal | Pattern | Matches / fails on |
|---|---|---|
| Quick sanity check | ^\S+@\S+\.\S+$ | [email protected] passes; "a @b.co" fails (space) |
| Practical validation | ^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$ | [email protected] passes; a@b fails (no TLD) |
| Per-user part, quoted forms allowed | ^"?[\w.%+-]+"?@[\w.-]+\.\w{2,}$ | "john o'hara"@x.com passes; double @ fails |
Why not one perfect email regex? Legal addresses can contain quoted local parts, IP-literal domains and comments. The practical pattern above covers 99%+ of real-world signups; the confirmation email covers the rest.
URL regex: http(s) links without the false positives
A URL pattern can balloon quickly once you try to honor every RFC 3986 detail. The versions below cover what form fields and log parsers actually need: an optional scheme, a host, and an optional path, query string and fragment. The match-anywhere variant is the one to use when you are extracting links from text rather than validating a whole input.
| Goal | Pattern | Matches / fails on |
|---|---|---|
| Validate a full URL | ^https?:\/\/[\w.-]+(?:\.[a-z]{2,})(?:[\/\w .-]*)*\??(?:[\w&=%.\-]*)?(?:#[\w\-]*)?$ | https://example.com/a?b=1#top passes; example.com (no scheme) fails — add an optional scheme group if wanted |
| Scheme optional | ^(https?:\/\/)?[\w-]+(\.[\w-]+)+([\/?#].*)?$ | example.com/path passes, http://example.com passes; "a b" fails |
| Find URLs inside text | https?:\/\/[^\s"'<>]+ | Extracts https://x.dev/a_1 from any string; stops at the first space or quote |
For extraction, the lazy class [^\s"'<>]+ is the workhorse: it grabs everything up to the first whitespace or angle bracket, which is where URLs end in HTML and prose.
Phone number regex: US formats and international E.164
Phone regex fails when it tries to be too clever about what humans type. Decide your contract first: if the field is "US phone, any common format", use the first pattern; if you store to a database, prefer E.164 (the international standard) and validate that instead. Formatting variants like 1 (555) 123-4567 ext. 88 all pass the US pattern because the separators are optional classes, not literals.
| Goal | Pattern | Matches / fails on |
|---|---|---|
| US, any common format | ^(\+?1[-.\s]?)?(\(?\d{3}\)?[-.\s]?)\d{3}[-.\s]?\d{4}$ | (555) 123-4567, 555.123.4567, +1 555-123-4567 all pass; 555-12-4567 fails |
| International E.164 | ^\+?[1-9]\d{1,14}$ | +14155552671 passes; 011 (leading zero) fails — E.164 forbids it |
| Digits with optional extensions | ^\d{10}(?:x\d{1,5})?$ | 5551234567x88 passes; (555) fails (no brackets in this contract) |
The US pattern accepts mismatched brackets like (555-123-4567. If you care, validate bracket pairing in application code — regex purity is not worth it here.
Date regex: formats and the leap-year trap
Regex can enforce the shape of a date and even most of its calendar rules, but no simple regex fully validates leap years — the strict ISO pattern below is the exception, and it is included because the leap-year branch is a classic teaching example. For everything else, match the shape with regex and confirm real calendar validity with your language's date parser.
| Goal | Pattern | Matches / fails on |
|---|---|---|
| ISO 8601 YYYY-MM-DD | ^\d{4}-(0[1-9]|1[0-2])-(0[1-9]|[12]\d|3[01])$ | 2026-10-26 passes; 2026-13-01 fails (month 13) |
| ISO with leap years | ^(?:(?!0000)[0-9]{4}-(?:(?:0[1-9]|1[0-2])-(?:0[1-9]|1\d|2[0-8])|(?:0[13-9]|1[0-2])-(?:29|30)|(?:0[13578]|1[02])-31)|(?:[0-9]{2}(?:0[48]|[2468][048]|[13579][26])|(?:0[48]|[2468][048]|[13579][26])00)-02-29)$ | 2024-02-29 passes; 2023-02-29 fails (not a leap year) |
| US MM/DD/YYYY | ^(0?[1-9]|1[0-2])\/(0?[1-9]|[12]\d|3[01])\/\d{4}$ | 10/26/2026 and 1/5/2026 pass; 2026/10/26 fails (wrong order) |
| Time HH:MM (24h) | ^([01]\d|2[0-3]):[0-5]\d$ | 23:59 passes; 24:00 fails |
Use the leap-year branch when the regex is the only gate (config files, quick scripts). In application code, a Date constructor or datetime parser is cheaper and clearer.
Password strength regex: one pattern per rule
Two adjustments are common. To require at least three of four character classes instead of all four, use alternation of lookaheads instead of a single chain. To allow any special character rather than a fixed list, replace the class with a negated alphanumeric class — but be careful, because "any non-alphanumeric" includes spaces, which most password policies do not want to permit silently.
const strongPassword =
/^(?=.*\d)(?=.*[a-z])(?=.*[A-Z])(?=.*[@#$%^&+=!]).{8,24}$/;
strongPassword.test("Ship1t@26"); // true
strongPassword.test("shipit2026"); // false — no uppercase, no special
strongPassword.test("SH1T@"); // false — under 8 charactersNumbers, money and decimals
Number formats look trivial and are not: negative signs, thousand separators, optional decimals and currency symbols each double the pattern's size. The versions below are the ones that survive contact with real form input. Note that the comma-separated pattern intentionally requires groups of exactly three digits, which is what separates 1,234,567 from accidentally matching 12,34.
| Goal | Pattern | Matches / fails on |
|---|---|---|
| Integer, optional sign | ^[+-]?\d+$ | -42 passes; 4.2 fails |
| Decimal, optional sign | ^[+-]?\d*\.?\d+$ | .5, 5., 3.14 pass; empty string fails |
| Money with commas | ^\$?\d{1,3}(?:,\d{3})*(?:\.\d{2})?$ | $1,234.56 passes; $12,34.5 fails (bad grouping) |
| Percentage 0-100 | ^(100|[0-9]{1,2})(\.\d+)?%$ | 99.9% passes; 101% fails |
Text-processing workhorses: slugs, tags, colors, IPs
These are the patterns developers paste ten times a day. They are deliberately minimal — the goal is a reliable building block, not full spec compliance. The hex color pattern accepts both the 3-digit and 6-digit forms with optional # prefix; the IPv4 pattern validates octet ranges arithmetically instead of enumerating 0-255 character by character, which keeps it readable.
| Goal | Pattern | Matches / fails on |
|---|---|---|
| URL slug | ^[a-z0-9]+(?:-[a-z0-9]+)*$ | my-page-2026 passes; My--Page fails (double dash) |
| HTML tag (extraction) | <\/?([a-z][a-z0-9]*)\b[^>]*> | Finds <div>, </p>, <br/>; group 1 holds the tag name |
| Hex color | ^#?(?:[0-9a-fA-F]{3}|[0-9a-fA-F]{6})$ | #FFF, fff, #1A2B3C pass; #12345 fails |
| IPv4 | ^(?:(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)\.){3}(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)$ | 192.168.1.1 passes; 256.1.1.1 fails |
| Whitespace run | \s+ | Split or collapse any mix of spaces, tabs and newlines |
| Trailing punctuation | [.,;:!?]+$ | Trims sentence-end punctuation from a token |
Parsing HTML with regex is fine for extracting simple tags; it is not fine for rendering or security decisions. For anything structural, use a real DOM parser — and sanitize any HTML you render (this site's tools run every rendered document through DOMPurify for exactly that reason).
How to adapt a pattern to your language
The patterns above are written in JavaScript/ECMAScript flavor. Most translate by copy-paste, but three differences bite people regularly. First, delimiters: JavaScript often shows patterns wrapped in forward slashes, while Python's re module takes the bare pattern string — drop the slashes and any trailing flags when porting. Second, backslash escaping: in a Python raw string r"\d" reaches the engine intact, but in a normal JavaScript string literal you need "\\d" — which is why the code examples on this page double the backslashes while the tables show the raw pattern.
Third, lookaround support: lookbehind (?<=...) landed in ECMAScript 2018, so older Safari versions throw on patterns that work everywhere else. Go's regexp engine (RE2) omits lookarounds entirely by design for performance guarantees — when you need one in Go, invert the logic and match the surrounding context instead. Python's re module supports lookaheads and fixed-width lookbehinds; the third-party regex module relaxes the fixed-width limit.
Test before you ship: the two-minute loop
Every pattern in this library is one paste away from a live match view. The fastest workflow: open the regex tester, paste the pattern, paste a few lines of real (and adversarial) sample text, and check the highlight and capture groups. Adversarial samples matter more than happy-path ones — the phone pattern that matches every example in your test file may still accept (555-123-4567 with a mismatched bracket.
When a match misbehaves, the usual suspects are greediness (add the lazy ?), the m flag changing what ^ and $ mean, and forgetting that the dot does not cross newlines without the s flag. The cheat sheet explains each of these tokens in depth with examples, and the diff checker is handy for eyeballing what a find-and-replace actually changed across a file.
- Happy-path sample plus three adversarial ones: too short, wrong separator, malicious input.
- Check capture groups — the text you extract is usually group 1, not the whole match.
- Test the replace view before running a regex against production files.
Questions this guide answers
- common regex patterns
- regex pattern
- regex email validation
- regex for url
- regex phone number
- regex date pattern
- regex generator
Frequently asked questions
What is a regex pattern?
A regex pattern is a compact template that describes a set of matching strings: character classes describe allowed characters, quantifiers describe repetition, anchors describe positions and groups capture parts of the match. The pattern ^\d{4}$ for example matches any string of exactly four digits such as 2026, and fails on 203 or 20261.
Which email regex should I use?
Use the practical pattern ^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$ for signups, and always confirm with a verification email. A fully RFC-compliant pattern is thousands of characters long, rejects legal addresses when written naively, and still cannot prove the mailbox exists. Structure-check with regex, existence-check with email.
How do I make a regex case-insensitive?
Add the i flag. In JavaScript that is /pattern/i or new RegExp(pattern, "i"); in Python, re.compile(pattern, re.IGNORECASE). For one-off case-insensitive sections inside a larger case-sensitive pattern, wrap the relevant part in a character class like [Ss], or use the scoped inline flag (?i:...) in engines that support it.
Why does my regex match too much?
Almost always greediness. A greedy quantifier like .* grabs as much as possible, so <.+> swallows <b>x</b> whole. Make it lazy with an extra question mark — <.+?> matches just <b> — or exclude the terminator from the class, as in [^\s"'<>]+ for URL extraction. The cheat sheet has worked examples of both fixes.
Can regex validate dates including leap years?
Yes — the ISO pattern in the date section above includes a full leap-year branch and correctly rejects 2023-02-29 while accepting 2024-02-29. It is long but mechanical: ordinary months, then 30-day months, then February 29 permitted only in divisible-by-4 years with the century exceptions. For application code, a native date parser is usually the better tool.
Do these patterns work in Python and Go?
Mostly yes, with flavor notes. Python's re module supports everything here including lookaheads; remember to use raw strings (r"...") so backslashes survive. Go's RE2 engine does not support lookarounds, so the password pattern needs restructuring there — match each requirement separately instead of chaining lookaheads. JavaScript needs the patterns as shown; older Safari builds additionally lack lookbehind.