Regex is one of those things that's extremely useful once you have it, and extremely easy to forget the moment you stop using it. Most developers don't need to memorize the full syntax — they need a reference they trust that they can grab patterns from quickly.
This post is that reference. Every pattern here is real, tested, and explained. At the bottom there's a link to DevCrate's Regex Studio where you can paste any pattern and test it against your own input immediately.
Quick syntax refresher
Before the patterns — a brief reminder of the building blocks.
. any character except newline
\d digit (0–9)
\D non-digit
\w word character (a-z, A-Z, 0-9, _)
\W non-word character
\s whitespace (space, tab, newline)
\S non-whitespace
^ start of string
$ end of string
\b word boundary
[abc] character class: a, b, or c
[^abc] negated class: not a, b, or c
[a-z] range: a through z
(abc) capture group
(?:abc) non-capturing group
a|b alternation: a or b
Quantifiers
* 0 or more (greedy)
+ 1 or more (greedy)
? 0 or 1
{n} exactly n
{n,} n or more
{n,m} between n and m
*? 0 or more (lazy)
+? 1 or more (lazy)
Flags (JavaScript)
i case-insensitive
g global (find all matches)
m multiline (^ and $ match line start/end)
s dotAll (. matches newline too)
Email addresses
There is no single "correct" email regex — the RFC is notoriously complex and full email validation is best left to a library or a confirmation link. For most use cases, you need something that catches obvious mistakes without being too strict.
// Practical — catches most real addresses
/^[^\s@]+@[^\s@]+\.[^\s@]+$/
// Stricter — requires valid TLD characters, no consecutive dots
/^[a-zA-Z0-9._%+\-]+@[a-zA-Z0-9.\-]+\.[a-zA-Z]{2,}$/
The first pattern reads as: one or more characters that aren't whitespace or @, then @, then the same again, then a dot, then the same again. It'll catch user@example.com, user+tag@sub.domain.io, and will correctly reject notanemail and @nodomain.
URLs
// Match http/https URLs
/https?:\/\/[^\s/$.?#].[^\s]*/i
// Match any URL including bare domains
/(?:https?:\/\/)?(?:www\.)?[-a-zA-Z0-9@:%._+~#=]{1,256}\.[a-zA-Z0-9()]{1,6}\b(?:[-a-zA-Z0-9()@:%_+.~#?&/=]*)/i
// Extract just the domain from a full URL
/(?:https?:\/\/)?(?:www\.)?([^\/\s?#]+)/i
// → match[1] is the domain
URL regex is notoriously tricky. For most validation tasks — form input, user-submitted links — it's often cleaner to use new URL(input) in JavaScript and catch the error if it's invalid. The regex above is best used for extracting URLs from arbitrary text, not strict validation.
Phone numbers
// US phone — accepts (555) 555-5555, 555-555-5555, 5555555555
/^[\+]?[(]?[0-9]{3}[)]?[-\s\.]?[0-9]{3}[-\s\.]?[0-9]{4,6}$/
// International — starts with + and country code
/^\+?[1-9]\d{6,14}$/
// Strip all non-digits before matching (recommended)
const digits = phone.replace(/\D/g, '');
/^\d{10}$/.test(digits); // US number
The strip-then-match approach is usually the most practical. Users enter phone numbers in too many formats to catch them all with a single pattern — normalizing first makes the validation much simpler.
Dates
// ISO 8601 — 2026-04-15
/^\d{4}-(0[1-9]|1[0-2])-(0[1-9]|[12]\d|3[01])$/
// US format — 04/15/2026
/^(0[1-9]|1[0-2])\/(0[1-9]|[12]\d|3[01])\/\d{4}$/
// Any separator — 2026-04-15 or 2026/04/15 or 2026.04.15
/^\d{4}[\/\-\.](0[1-9]|1[0-2])[\/\-\.](0[1-9]|[12]\d|3[01])$/
These patterns validate format but not logical validity — they'll accept February 31st. For actual date validation, parse with new Date() and check isNaN(), or use a library like date-fns.
IP addresses
// IPv4 — matches 0.0.0.0 to 255.255.255.255
/^(25[0-5]|2[0-4]\d|[01]?\d\d?)(\.(25[0-5]|2[0-4]\d|[01]?\d\d?)){3}$/
// IPv6 — full address
/^([0-9a-fA-F]{1,4}:){7}[0-9a-fA-F]{1,4}$/
The IPv4 pattern is worth understanding: 25[0-5] matches 250–255, 2[0-4]\d matches 200–249, and [01]?\d\d? matches 0–199.
URL slugs
// Validate a slug — lowercase letters, numbers, hyphens only
/^[a-z0-9]+(?:-[a-z0-9]+)*$/
// Generate a slug from a title
function slugify(str) {
return str
.toLowerCase()
.trim()
.replace(/[^\w\s-]/g, '') // remove non-word chars
.replace(/[\s_-]+/g, '-') // spaces/underscores → hyphens
.replace(/^-+|-+$/g, ''); // trim leading/trailing hyphens
}
Password rules
// At least 8 chars, one uppercase, one lowercase, one digit
/^(?=.*[a-z])(?=.*[A-Z])(?=.*\d).{8,}$/
// At least 8 chars, one uppercase, one lowercase, one digit, one special char
/^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[@$!%*?&\-_#^])[A-Za-z\d@$!%*?&\-_#^]{8,}$/
// Check individual rules (more user-friendly)
const rules = {
minLength: str => str.length >= 8,
hasUpper: str => /[A-Z]/.test(str),
hasLower: str => /[a-z]/.test(str),
hasDigit: str => /\d/.test(str),
hasSpecial: str => /[@$!%*?&\-_#^]/.test(str),
};
The individual rules approach is friendlier for UI — it lets you show a green checkmark per rule as the user types rather than a single pass/fail.
Hex colors
// 3 or 6 digit hex with #
/^#([A-Fa-f0-9]{6}|[A-Fa-f0-9]{3})$/
// 3, 4, 6, or 8 digit (includes alpha channel)
/^#([A-Fa-f0-9]{3,4}|[A-Fa-f0-9]{6}|[A-Fa-f0-9]{8})$/
// Extract hex colors from a CSS file
/(?<![a-zA-Z])#([A-Fa-f0-9]{6}|[A-Fa-f0-9]{3})\b/g
Common code patterns
// UUID v4
/^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i
// JWT — three base64url segments separated by dots
/^[A-Za-z0-9\-_]+\.[A-Za-z0-9\-_]+\.[A-Za-z0-9\-_]+$/
// Hex string (any length)
/^[0-9a-fA-F]+$/
// Alphanumeric with underscores and hyphens (good for IDs/usernames)
/^[a-zA-Z0-9_\-]+$/
Extracting things from text
// Extract all URLs from text
const urls = text.match(/https?:\/\/[^\s]+/g) || [];
// Extract all hashtags
const tags = text.match(/#[a-zA-Z]\w*/g) || [];
// Extract all @mentions
const mentions = text.match(/@[a-zA-Z0-9_]+/g) || [];
// Extract all numbers (including decimals and negatives)
const numbers = text.match(/-?\d+(?:\.\d+)?/g) || [];
// Find duplicate words
/\b(\w+)\s+\1\b/gi
Lookaheads and lookbehinds
These let you match something based on what comes before or after it, without including that context in the match.
// Lookahead: match "price" only if followed by a digit
/price(?=\s*\d)/i
// Negative lookahead: match "http" only if NOT followed by "s"
/http(?!s)/
// Lookbehind: match digits only if preceded by "$"
/(?<=\$)\d+(\.\d{2})?/
// Practical: extract value from "amount: 42.50"
const match = text.match(/(?<=amount:\s*)\d+(\.\d+)?/i);
const amount = match ? parseFloat(match[0]) : null;
Lookbehinds are supported in modern JavaScript (Node 10+, Chrome 62+, Safari 2019+). If you need older browser support, use capture groups instead.
A few things worth remembering
Greedy vs lazy matching trips people up constantly. .* is greedy — it matches as much as possible. .*? is lazy — it stops at the first opportunity. If you're extracting content between tags and getting too much, switching from .* to .*? usually fixes it.
Escaping in strings adds a layer of confusion. In a JavaScript string literal, \d needs to be written as \\d if you're passing the pattern as a string to new RegExp(). In a regex literal (/\d/), no double escaping needed.
Test with real data, not invented examples. Edge cases worth testing every time: empty string, all whitespace, Unicode characters, very long strings, strings with newlines.
Test these patterns in your browser
DevCrate's Regex Studio lets you paste any pattern and test it against real input in your browser — with live match highlighting, capture group inspection, and flag toggles. No install, no account, nothing leaves your machine.
Top comments (0)