JavaScript Regular Expressions: A Practical Cheat Sheet

By the CodeBeautify team at Softaware Commerce Ltd · Published

A JavaScript regular expression is a pattern written as /pattern/flags or built with new RegExp('pattern', 'flags'), and used with methods such as test(), match(), matchAll() and replace(). This cheat sheet lists the syntax you reach for most often, then gives eleven practical patterns that were each run in Node.js 20 against inputs that should and should not match, with their limits stated. You can try any of them in the Regex Tester.

Character classes

SyntaxMatches
.Any character except line terminators (unless the s flag is set)
\d / \DAn ASCII digit 0–9 / anything else
\w / \WAn ASCII letter, digit or underscore / anything else
\s / \SWhitespace, including spaces, tabs, line breaks and Unicode spaces / anything else
[abc]One of a, b or c
[^abc]Any character except a, b or c
[a-z0-9]A character in either range
\p{L}, \p{Lu}A Unicode letter, an uppercase letter (needs the u or v flag)

\d, \w and \b are ASCII-only even with the u flag: 'café latte'.match(/\b\w+\b/g) returns ['caf', 'latte']. Use \p{L} with u when you need letters from any script.

Anchors and boundaries

SyntaxMatches at
^Start of input, or start of any line with the m flag
$End of input, or end of any line with the m flag
\bA word boundary, between a \w and a \W character
\BAny position that is not a word boundary

Unlike some other engines, a JavaScript $ without m does not match before a trailing newline: /^\d{3}$/.test('123\n') is false.

Quantifiers: greedy and lazy

GreedyLazyRepeats
**?0 or more
++?1 or more
???0 or 1
{n}–Exactly n
{n,}{n,}?n or more
{n,m}{n,m}?Between n and m

A greedy quantifier takes as much as it can and gives characters back only if the rest of the pattern fails; a lazy one takes as little as possible.

'<b>x</b><b>y</b>'.match(/<b>.*<\/b>/)[0]   // '<b>x</b><b>y</b>'
    '<b>x</b><b>y</b>'.match(/<b>.*?<\/b>/)[0]  // '<b>x</b>'

Groups and backreferences

SyntaxMeaning
(abc)Capturing group, numbered from 1 by its opening bracket
(?:abc)Non-capturing group: groups for quantifiers or alternation without capturing
(?<name>abc)Named capturing group, available as match.groups.name
\1, \k<name>Backreference: the same text the group captured
a|bAlternation: a or b
$1, $<name>, $&In a replace() string: group 1, a named group, the whole match
const m = /(?<year>\d{4})-(?<month>\d{2})-(?<day>\d{2})/.exec('Due 2026-10-03');
    m.groups.year;  // '2026'
    '2026-10-03'.replace(/(?<y>\d{4})-(?<m>\d{2})-(?<d>\d{2})/, '$<d>/$<m>/$<y>');  // '03/10/2026'

Lookahead and lookbehind

Lookarounds test what comes before or after a position without including it in the match.

SyntaxMeaningExample
X(?=Y)X followed by Y/\w+(?=\.js$)/ on file.test.js gives test
X(?!Y)X not followed by Y/q(?!u)/gi on Iraq quit matches only the q in Iraq
(?<=Y)XX preceded by Y/(?<=£)\d+/g on £30, €45, £7 gives 30, 7
(?<!Y)XX not preceded by Y/(?<!\$)\d/g on a1 $2 b3 gives 1, 3

Flags

FlagNameEffect
gglobalFind all matches; required by matchAll() and replaceAll() with a regex
iignoreCaseCase-insensitive matching
mmultiline^ and $ match at line starts and ends
sdotAll. also matches line terminators (ES2018)
uunicodeCode-point matching, \p{…} escapes, stricter syntax (ES2015)
ystickyMatch only at exactly lastIndex (ES2015)
dhasIndicesAdds start and end positions of each group as match.indices (ES2022)
vunicodeSetsAn upgraded u with set operations such as [\p{L}--[a-z]] (ES2024); cannot be combined with u

Node.js 20 accepts all eight; d and v are the newest, so check your target browsers before relying on them. Inline modifiers such as (?i:…) and the RegExp.escape() function are more recent still and are not available in Node.js 20. The Regex Tester on this site has checkboxes for g (on by default), i, m, s and u.

Escaping

These characters have special meaning and need a backslash to match literally: . * + ? ^ $ { } ( ) | [ ] \ / (the slash only inside a /…/ literal). Inside a character class (without the v flag), only \, ], and ^ at the start or - between characters, are special. To use arbitrary text as a literal pattern:

const escapeRegExp = (s) => s.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
    new RegExp(escapeRegExp('1.5*(x)')).test('cost 1.5*(x)');  // true

Practical patterns, tested

Each pattern below was run in Node.js 20 with the positive and negative examples shown.

TaskPatternMatchesRejects
ISO 8601 calendar date/^\d{4}-(0[1-9]|1[0-2])-(0[1-9]|[12]\d|3[01])$/2026-10-03, 2024-02-292026-13-01, 2026-1-3, 2026/10/03
Hex colour/^#(?:[0-9a-f]{3,4}|[0-9a-f]{6}|[0-9a-f]{8})$/i#fff, #4F46E5, #4f46e5ccfff, #fffff, #ggg
Email sanity check/^[^\s@]+@[^\s@]+\.[^\s@]+$/[email protected], [email protected]hello@example, @example.com, hello@@example.com
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)$/192.168.0.1, 255.255.255.255256.1.1.1, 1.2.3, 01.2.3.4
Semantic version (simplified)/^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(?:-([0-9A-Za-z.-]+))?(?:\+([0-9A-Za-z.-]+))?$/1.2.3, 1.0.0-rc.1+sha.abc1.2, 01.2.3, v1.2.3
Repeated word/\b(\w+)\s+\1\b/githis is is a test, The the catthis is a test, is island

Limitations, honestly stated:

  • Date: checks shape, not the calendar, so 2023-02-31 passes. Confirm with a date library or by round-tripping through Date.
  • Email: this only catches obvious typos. It accepts [email protected] and rejects the RFC 5322-valid "john smith"@example.com. Fully validating RFC 5322 addresses with a regex is impractical; the only real test is sending a confirmation message.
  • IPv4: rejects leading zeros on purpose, because some parsers read them as octal. It does not handle IPv6.
  • Semantic version: accepts some prerelease strings the specification forbids, such as 1.0.0-01. semver.org publishes a full pattern if you need strictness.
  • Repeated word: \w is ASCII-only, so accented words are not handled.

Replacement and extraction patterns

// Trim whitespace at both ends (String.prototype.trim() does the same)
    '  \t hello world \n '.replace(/^\s+|\s+$/g, '');          // 'hello world'

    // Collapse runs of whitespace, including newlines, to one space
    'a  b\t\tc\n d'.replace(/\s+/g, ' ');                       // 'a b c d'

    // Remove duplicated words
    'Paris in the the spring'.replace(/\b(\w+)\s+\1\b/gi, '$1'); // 'Paris in the spring'

    // Thousands separators for an integer string
    '1234567'.replace(/\B(?=(\d{3})+(?!\d))/g, ',');            // '1,234,567'

    // URL slug
    '  Cron Syntax: Explained! '.toLowerCase()
      .replace(/[^a-z0-9]+/g, '-').replace(/^-+|-+$/g, '');     // 'cron-syntax-explained'

    // Query parameters
    const url = 'https://example.com/search?q=cron%20syntax&page=2&debug#top';
    [...url.matchAll(/[?&]([^=&#]+)=?([^&#]*)/g)].map(m => [m[1], m[2]]);
    // [['q', 'cron%20syntax'], ['page', '2'], ['debug', '']]

The thousands pattern breaks on decimals (1234.5678 becomes 1,234.5,678), so prefer Intl.NumberFormat for real numbers. The slug pattern drops accented letters (Crème brûlée becomes cr-me-br-l-e), so normalise text first if that matters. The query-string pattern does not percent-decode values; new URL(url).searchParams does, and handles edge cases a regex will miss.

Common pitfalls

Catastrophic backtracking

Nested quantifiers that can match the same text in many ways make the engine try every combination before failing. /^(a+)+$/ tested against a run of a characters followed by ! roughly doubles its running time with every extra a; in our Node.js 20 test, 28 characters already took several seconds, while the equivalent /^a+$/ answered instantly. Avoid patterns such as (x+)+, (x|x)* or (.*,)*, and never run user-supplied patterns on a server without a time limit. The Regex Tester re-runs your pattern in the page on every keystroke, so a pattern like this can make the browser tab unresponsive.

Forgetting the g flag

replace() with a non-global regex changes only the first match ('a1b2'.replace(/\d/, '#') gives 'a#b2'). matchAll() and replaceAll() throw a TypeError if the regex lacks g.

lastIndex with g or y and test()

A global or sticky regex remembers where it stopped in lastIndex, so reusing it gives alternating results:

const re = /cat/g;
    re.test('cat');  // true  (lastIndex is now 3)
    re.test('cat');  // false (search starts at 3, fails, lastIndex resets to 0)
    re.test('cat');  // true

Drop the g flag for yes/no checks, or reset re.lastIndex = 0 before each call.

Escaping inside new RegExp strings

A string literal consumes one level of backslashes before the regex engine sees it. new RegExp('\d+') produces the pattern d+, matching the letter d. Write new RegExp('\\d+') or new RegExp(String.raw`\d+`). In the Regex Tester you type the pattern itself, without surrounding slashes or string escaping.

Using the Regex Tester

Type a pattern and test string and the tester highlights every match as you type, with a match count in the Test string header. The Matches panel lists each match with its number, its index in the text and the captured groups as a JSON array in numeric order; named groups appear by position, and a group that did not take part in the match shows as null. With g unticked only the first match is listed. Filling in Replace with switches the panel to a replacement result, using JavaScript's replace(), so $1, $<name> and $& all work.

Frequently asked questions

What is the difference between test(), match() and exec()?

test() returns true or false. exec() returns one match with its groups and index, advancing lastIndex with g or y. match() returns the first match with groups, or with g an array of every matched string without groups; use matchAll() to get every match with its groups.

Does JavaScript support lookbehind?

Yes. Both (?<=…) and (?<!…) are part of ES2018 and work in current browsers and Node.js.

How do I make the dot match newlines?

Add the s flag, or use [\s\S] in engines that predate it.

Can I validate an email address with a regex?

Only roughly. A simple pattern catches typos such as a missing @, but the full address grammar is too complex to express practically, and a well-formed address may still not exist. Send a verification email for anything that matters.

Tools for this guide