In short
- This regex cheat sheet covers JavaScript's RegExp syntax, which browsers and Node.js share. Most of it also works in Python, PHP and Java.
- Quantifiers are greedy by default. Add ? after one (such as *? or +?) to match as little as possible.
- JavaScript has eight flags: d, g, i, m, s, u, v and y. The u and v flags cannot be used together.
- Validate structure with regex, then check meaning in code: a date pattern can't know that 30 February doesn't exist.
- Nested quantifiers such as (a+)+ can take exponential time on near-matches. Avoid them on user input.
How to read this regex cheat sheet
A regular expression is a pattern that describes text, and this cheat sheet lists the pieces in JavaScript syntax. Everything here works in current browsers and Node.js, and every example in the patterns table was run in Node.js 22 before publishing.
Write a pattern as a literal between slashes, /\d+/g, or build it from a string with new RegExp("\\d+", "g"). In the string form every backslash has to be doubled, which is the most common reason a pattern that works in a tester fails in code. To try any row below, paste it into the regex tester, which highlights matches and lists capture groups as you type. For structured data such as JSON or CSV, a parser beats a regex; our JSON vs YAML vs CSV guide explains each format.
Anchors and character classes
Anchors match a position, not a character. Character classes match exactly one character from a set.
| Token | Matches | Example |
|---|---|---|
^ | Start of the text (start of each line with the m flag) | ^Hello matches Hello only at the start |
$ | End of the text (end of each line with m) | \.pdf$ matches names ending in .pdf |
\b | A word boundary: the point between a word character and a non-word character | \bcat\b finds cat but not category |
\B | A position that is not a word boundary | \Bcat finds the cat inside concat |
. | Any character except a line break (any character at all with s) | a.c matches abc and a-c |
\d \D | A digit 0-9, and anything that is not a digit | \d{4} matches 2026 |
\w \W | A letter, digit or underscore (ASCII only), and the opposite | \w+ matches user_42 |
\s \S | Whitespace (space, tab, line break and Unicode spaces), and the opposite | \s+ matches a run of spaces |
[abc] [^abc] | One character from the set, or one not in it | [aeiou] matches any vowel |
[a-z0-9] | A range of characters | [A-F0-9] matches one hex digit |
\p{L} | Any Unicode letter (needs the u or v flag) | \p{L}+ matches naïve in full |
Inside square brackets most symbols lose their special meaning, so [.?] matches a literal dot or question mark. You still need to escape ], \ and ^ at the start, and put - first or last to mean a hyphen. \w only covers ASCII letters, so it stops at é. Use [\p{L}\d_] with the u flag for text in other languages.
Quantifiers: greedy vs lazy
A quantifier says how many times the item before it may repeat. By default each one is greedy: it takes as much as it can, then gives characters back only if the rest of the pattern fails.
| Greedy | Lazy | Meaning |
|---|---|---|
* | *? | 0 or more |
+ | +? | 1 or more |
? | ?? | 0 or 1 (optional) |
{3} | {3} | Exactly 3 (lazy makes no difference) |
{2,} | {2,}? | 2 or more |
{2,5} | {2,5}? | Between 2 and 5 |
The difference shows up as soon as a line holds two matches. On <b>bold</b>, the greedy <.*> matches the whole string, from the first < to the last >. The lazy <.*?> stops at the first > and matches only <b>. <[^>]*> gets the same result with less backtracking.
Lazy quantifiers can surprise you when nothing follows them. /a*?/g matches an empty string at every position, so replacing with it turns aaa into -a-a-a-.
Groups, backreferences and lookarounds
Groups apply a quantifier to several characters, capture text and hold alternatives together. Lookarounds check what comes before or after a position without including it in the match.
| Syntax | What it does | Replacement token |
|---|---|---|
(abc) | Capturing group, numbered from 1 by its opening bracket | $1, $2 |
(?:abc) | Groups without capturing, for applying a quantifier or alternation | None |
(?<year>\d{4}) | Named capturing group | $<year> |
\1 \k<year> | Backreference: the same text a group already matched | Not used in replacements |
a|b | Alternation: a or b | None |
(?=x) | Lookahead: followed by x, without consuming it | None |
(?!x) | Negative lookahead: not followed by x | None |
(?<=x) | Lookbehind: preceded by x | None |
(?<!x) | Negative lookbehind: not preceded by x | None |
| Whole match | The entire matched text | $& |
| Literal dollar | A dollar sign in the replacement | $$ |
JavaScript supports lookbehind of any length, so (?<=price:\s*)\d+ works. Python's re module rejects it with "look-behind requires fixed-width pattern". Python also writes named groups as (?P<name>...), and its $ matches before a final line break, so ^\d{3}$ matches 123 followed by a newline in Python but not in JavaScript.
The replacement tokens work the same in String.replace(), in the regex tester's Replacement field and in the Regex mode of the find and replace tool.
JavaScript regex flags: g, i, m, s, u, v, y and d
Flags go after the closing slash (/x/gi) or as the second argument to new RegExp(). The flags property always lists them in alphabetical order, so /x/yig.flags returns giy.
| Flag | Property | What it changes |
|---|---|---|
g | global | Find every match instead of the first. Also makes the regex remember lastIndex. |
i | ignoreCase | Case-insensitive matching. |
m | multiline | ^ and $ match at line breaks as well as at the ends of the text. |
s | dotAll | . also matches line breaks. |
u | unicode | Treats characters outside the Basic Multilingual Plane, such as emoji, as one character and enables \p{...} escapes. |
v | unicodeSets | An upgrade of u: adds set operations in classes, such as [\p{L}--[a-z]], and properties of strings. Cannot be combined with u. |
y | sticky | Matches only at exactly lastIndex, without searching ahead. |
d | hasIndices | Adds start and end positions for the match and each group to exec() results, in .indices. |
Two behaviours catch people out. First, a regex with g remembers where it stopped, so calling test() twice on the same string returns true and then false. Reset lastIndex to 0, or drop g when you only need a yes or no. Second, new RegExp('a', 'uv') throws a SyntaxError, because the two flags read classes differently. According to MDN, the v flag has worked in all major browsers since September 2023, so pick it over u for new code that needs Unicode sets.
Regex examples: 15 tested patterns
Each pattern was checked in Node.js 22 against the strings in its notes, including inputs it should reject.
| Task | Pattern | Notes from testing |
|---|---|---|
| Email, rough check | ^[^\s@]+@[^\s@]+\.[^\s@]+$ | Accepts ana@example.com and a.b+tag@mail.co.uk, rejects no-at.com and two@@x.com. It also accepts a@b.c. Treat it as a typo catcher. |
| URL starting with http or https | ^https?:\/\/[^\s/$.?#].[^\s]*$ with i | Accepts https://toolzylab.com/blog/ and http://localhost:3000, rejects ftp://x.com and an address with a space. Use new URL() when you need to parse it. |
| ISO date (YYYY-MM-DD) | ^\d{4}-(0[1-9]|1[0-2])-(0[1-9]|[12]\d|3[01])$ | Rejects month 13 and 2026-1-5, but accepts 2026-02-30. Check real calendar validity in code. |
| Reorder a date | (?<y>\d{4})-(?<m>\d{2})-(?<d>\d{2}) replaced with $<d>/$<m>/$<y> | Turns 2026-10-11 into 11/10/2026. |
| Phone in E.164 format | ^\+[1-9]\d{6,14}$ | Accepts +14155550123 and +923001234567. Rejects spaces, a leading 0 after the plus, and more than 15 digits. |
| US phone, loose | ^\(?(\d{3})\)?[\s.-]?(\d{3})[\s.-]?(\d{4})$ | Accepts (415) 555-0123, 415.555.0123 and 4155550123. Groups 1-3 give the parts for reformatting. |
| Trim whitespace | ^\s+|\s+$ with g, replaced with nothing | Same result as str.trim(), which is clearer in real code. |
| Collapse repeated spaces | {2,} with g, replaced with one space | Turns any run of two or more spaces into a single space. |
| Duplicate words | \b(\w+)\s+\1\b with gi | Finds is is and the the. The closing \b stops it flagging the theory. |
| Hex color | ^#(?:[0-9a-f]{3}){1,2}$ with i | Accepts #fff and #1A2b3C, rejects #abcd and fff. |
| IPv4 address | ^(?:(?: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)$ | Accepts 192.168.1.1 and 255.255.255.255, rejects 256.1.1.1 and 01.2.3.4. |
| Password rule | ^(?=.*[a-z])(?=.*[A-Z])(?=.*\d).{12,}$ | Each lookahead checks one rule: a lowercase letter, an uppercase letter, a digit, and 12 or more characters overall. |
| Number after a dollar sign | (?<=\$)\d+(?:\.\d{2})? with g | From Was $49.99, now $35 it returns 49.99 and 35, without the dollar signs. |
| URL slug | ^[a-z0-9]+(?:-[a-z0-9]+)*$ | Accepts regex-cheat-sheet, rejects capitals, double hyphens and a leading hyphen. |
| Thousands separators | \B(?=(\d{3})+(?!\d)) with g, replaced with a comma | Turns 1234567 into 1,234,567. toLocaleString() does this with locale rules. |
On email: browsers check <input type="email"> with a longer pattern from the HTML standard, and even that accepts a@b. No pattern can tell you an address exists. Use a simple check to catch typos, then send a confirmation email.
For the phone patterns, normalize first. Strip spaces, brackets and dashes with [\s().-] and the g flag, then test the digits.
Catastrophic backtracking (ReDoS) explained briefly
When a match fails, the engine goes back and tries every other way the quantifiers could have split the text. With a nested quantifier the number of ways explodes. ^(a+)+$ tested against 28 letter a's followed by a single other character took about 12 seconds in Node.js 22 on our machine, and every extra a roughly doubled the time. The flat version ^a+$ answered in under a millisecond.
Anyone who can send text to a server running such a pattern can freeze it with one request: a regular expression denial of service (ReDoS). To avoid it:
- Don't put a quantified group inside another quantifier, as in
(\w+\s?)*or(a|aa)+. - Make alternatives mutually exclusive, so only one branch can match a given character.
- Prefer negated classes like
"[^"]*"to".*?"when matching delimited text. - Limit input length before matching untrusted text.
The regex tester runs each match in a background worker and stops it after 2 seconds, so a runaway pattern shows a timed-out message instead of freezing the page. If a pattern times out there, it will do worse on a server. To check what a regex replace changed in a long document, compare before and after in the diff checker.
Frequently asked questions
What is the difference between greedy and lazy regex?
A greedy quantifier such as .* matches as much text as possible, and a lazy one such as .*? matches as little as possible. On <b>bold</b>, <.*> matches the whole string while <.*?> matches only <b>.
Does JavaScript regex support lookbehind?
Yes. JavaScript supports (?<=x) and (?<!x) lookbehind in all current browsers and Node.js, including variable-length lookbehind, which Python's re module does not allow.
Why does my regex test() return true and then false?
The regex has the g or y flag, so it stores lastIndex after each match and starts the next search from there. Remove the flag for a simple yes or no check, or set lastIndex back to 0 before each call.
What regex should I use to validate an email address?
A rough check such as ^[^\s@]+@[^\s@]+\.[^\s@]+$ catches most typos. No regex can confirm an address works, so send a confirmation email to be sure.
What is the difference between the u and v flags?
Both make a regex Unicode-aware. The v flag adds set operations inside character classes, such as [\p{L}--[a-z]] for letters other than a-z, and properties of strings. You can use one or the other, not both.
How do I use a regex in find and replace?
Capture the parts you want to keep with brackets, then refer to them in the replacement with $1, $2 or $<name>. For example, (\d{4})-(\d{2})-(\d{2}) replaced with $3/$2/$1 turns 2026-10-11 into 11/10/2026.