JavaScript Regex Cheat Sheet
Covers regular expression syntax, flags, common validation patterns, named capture groups, and the string and RegExp methods used to apply them.
Regex Syntax Basics
Core building blocks of a pattern.
/abc/ // Literal match/a|b/ // Alternation: a OR b/ab?/ // ? = 0 or 1 of preceding (matches "a" or "ab")/ab*/ // * = 0 or more/ab+/ // + = 1 or more/a{2,4}/ // Between 2 and 4 of preceding/^abc$/ // ^ start, $ end of string (or line, with /m flag)/[abc]/ // Character class: a, b, or c/[^abc]/ // Negated class: anything but a, b, c/./ // Any character except newline (unless /s flag)/\d \w \s/ // Digit, word char, whitespace ( \D \W \S = negated)/(abc)/ // Capturing group/(?:abc)/ // Non-capturing group/(?<year>\d{4})/ // Named capturing group
Testing & Matching
RegExp and String methods for applying patterns.
const re = /\d+/;re.test("order 42"); // true -- does it match at all?"order 42".match(re); // ["42", index: 6, ...] -- first match detailsconst global = /\d+/g;"a1 b22 c333".match(global); // ["1", "22", "333"] -- all matches, no groups[..."a1 b22 c333".matchAll(/\d+/g)]; // Array of full match objects (needs /g flag)"2024-01-15".replace(/(\d+)-(\d+)-(\d+)/, "$2/$3/$1");// "01/15/2024" -- $1, $2, $3 reference captured groups"a,b, c ,d".split(/\s*,\s*/); // ["a", "b", "c", "d"] -- split on pattern
Common Patterns
Pragmatic patterns for everyday validation.
const email = /^[\w.+-]+@[\w-]+\.[a-zA-Z]{2,}$/;const url = /^https?:\/\/[\w.-]+\.[a-zA-Z]{2,}(\/\S*)?$/;const digitsOnly = /^\d+$/;const whitespaceCollapse = /\s+/g; // "a b" -> "a b" via replace(re, " ")const usPhone = /^\(?\d{3}\)?[-.\s]?\d{3}[-.\s]?\d{4}$/;" hello world ".replace(whitespaceCollapse, " ").trim();// "hello world"// Note: these are pragmatic, not RFC-perfect (e.g. real email validation is far more complex)
Named Groups & Lookaround
Extract structured data and match with context.
const match = "2024-01-15".match(/(?<year>\d{4})-(?<month>\d{2})-(?<day>\d{2})/);match.groups.year; // "2024"match.groups.month; // "01"// Lookahead: match "foo" only if followed by "bar"/foo(?=bar)/.test("foobar"); // true/foo(?=bar)/.test("foobaz"); // false// Negative lookahead: match "foo" NOT followed by "bar"/foo(?!bar)/.test("foobaz"); // true// Lookbehind: match digits preceded by "$""$100".match(/(?<=\$)\d+/)[0]; // "100"
Flags & Methods Reference
Flags change how the whole pattern behaves.
- g- Global: find all matches, not just the first
- i- Case-insensitive matching
- m- Multiline: ^ and $ match the start/end of each line
- s- Dotall: . also matches newline characters
- u- Unicode: enables full Unicode code point matching
- y- Sticky: matches only from lastIndex, no scanning ahead
- str.search(re)- Returns the index of the first match, or -1
- re.exec(str)- Returns the next match; with /g, advances lastIndex on repeated calls
Catastrophic Backtracking (ReDoS)
Nested quantifiers over overlapping character sets can make matching time explode exponentially on crafted input.
// DANGEROUS: nested quantifiers with overlapping alternativesconst unsafe = /^(a+)+$/;unsafe.test("a".repeat(30) + "!"); // hangs the event loop -- exponential backtracking// SAFE rewrite: eliminate the nested quantifier, use an atomic-like groupingconst safe = /^a+$/;safe.test("a".repeat(30) + "!"); // returns false instantly// SAFER for user-supplied patterns: possessive-style via lookahead trick// (JS lacks native possessive quantifiers/atomic groups pre-2024)const pseudoAtomic = /^(?=(a+))\1$/;// Rule of thumb: avoid patterns like (a+)+, (a|a)+, (a|ab)* on attacker-controlled strings
Unicode Property Escapes
Match by Unicode category/script instead of hardcoded character ranges -- requires the /u flag.
const hasLetter = /\p{Letter}/u;hasLetter.test("café"); // true -- matches accented letters too, unlike [a-zA-Z]const emoji = /\p{Emoji_Presentation}/u;emoji.test("🎉"); // trueconst greekScript = /\p{Script=Greek}/u;greekScript.test("\u03B1"); // true (alpha)const notWhitespace = /\P{White_Space}/u; // uppercase P negates the property// Without /u, \p{...} is just a literal "p" followed by a quantifier-like group -- easy silent bug/\p{Letter}/.test("p{Letter}"); // true! -- /u flag omitted, treated as literal text
Building a Tokenizer with the Sticky Flag
The y flag anchors matching to lastIndex with no scanning ahead, ideal for hand-written lexers.
function tokenize(input) { const tokenSpecs = [ [/^\s+/y, null], [/^[0-9]+/y, "NUMBER"], [/^[a-zA-Z_]\w*/y, "IDENT"], [/^[+\-*/]/y, "OP"], ]; const tokens = []; let pos = 0; outer: while (pos < input.length) { for (const [re, type] of tokenSpecs) { re.lastIndex = pos; const match = re.exec(input); if (match) { if (type) tokens.push({ type, value: match[0] }); pos += match[0].length; continue outer; } } throw new SyntaxError(`Unexpected character at ${pos}: ${input[pos]}`); } return tokens;}tokenize("x + 42");// [{type:"IDENT",value:"x"},{type:"OP",value:"+"},{type:"NUMBER",value:"42"}]
Custom Matchers with Symbol.replace
Objects implementing the regex well-known symbols can be passed anywhere a RegExp is expected.
class RedactNumbers { [Symbol.replace](string) { return string.replace(/\d+/g, "[REDACTED]"); } [Symbol.match](string) { return /\d+/.test(string); }}"call 555-1234 now".replace(new RedactNumbers(), "");// "call [REDACTED]-[REDACTED] now" -- String.prototype.replace defers to Symbol.replace// Named group backreferences in the replacement string"2024-01-15".replace( /(?<y>\d{4})-(?<m>\d{2})-(?<d>\d{2})/, "$<d>/$<m>/$<y>");// "15/01/2024" -- $<name> reads named groups, an alternative to positional $1/$2
Advanced Reference
Behaviors and APIs that go beyond everyday pattern matching.
- d flag (hasIndices)- Adds a .indices array to match results giving [start, end] positions for the whole match and each group
- RegExp.prototype.source / flags- Read-only strings exposing the pattern text and active flags, useful for cloning a regex with new flags
- String.raw with dynamic patterns- Use new RegExp(String.raw`\d{${n}}`) to build patterns from variables without double-escaping backslashes
- matchAll requires /g- Calling matchAll on a non-global regex throws a TypeError; test/exec do not have this restriction
- v flag (ES2024 unicodeSets)- Superset of /u enabling set operations (\p{..}--\p{..}) and multi-character class syntax
- lastIndex reset trap- A /g or /y regex retains lastIndex between calls even across different input strings, causing skipped or false-negative matches
A /g regex object is stateful -- it remembers lastIndex between calls to .exec() or .test(), which causes intermittent bugs if you reuse the same regex instance across unrelated strings; create a fresh literal or reset lastIndex = 0 first.