A beginner-friendly guide to regex in JavaScript

Regular expressions are compact patterns for finding, extracting, and changing text. This guide covers the JavaScript syntax you will use most often.

What is a regular expression?

A regular expression, or regex, describes text you want to match.

This pattern matches the exact text hello:

/hello/

Use it with a JavaScript string method:

const greeting = 'Well, hello'
 
console.log(greeting.replace(/hello/, 'hi'))
// Well, hi

Regex is useful when a plain string is not flexible enough. It can match things like "a number at the end" or "every word between braces."

Creating a regex in JavaScript

JavaScript has two forms.

Regex literals

Write the pattern between forward slashes. Put optional flags after the closing slash:

const pattern = /hello/gi

Literals are best when the pattern is known while writing the code.

The RegExp constructor

Use the constructor when part of the pattern comes from a variable:

const word = 'hello'
const pattern = new RegExp(word, 'gi')

The first argument is a string. Backslashes inside it must be escaped:

const literal = /\d+/
const constructed = new RegExp('\\d+')

Both patterns match one or more digits.

Literal characters and escaping

Most characters match themselves. /cat/ matches cat.

These characters have special meaning outside a character class:

. ^ $ * + ? ( ) [ ] { } | \

Add a backslash to match one literally:

/\?/

That pattern matches a question mark.

A forward slash is not part of regex syntax itself, but it closes a JavaScript regex literal. Escape it inside a literal:

const protocol = /https:\/\//

You do not need to escape / in a constructor string:

const protocol = new RegExp('https://')

Inside [], the rules change. Backslash and ] need care. A hyphen creates a range unless it is escaped or placed first or last. A caret negates the class only when it comes first.

Character matching

The dot .

A dot matches any single character except a line terminator:

/c.t/

It matches cat, cut, and c3t. It does not match ct.

Use the s flag if the dot should also match line terminators:

console.log(/start.end/s.test('start\nend')) // true

Character classes []

A character class matches one character from a set:

/[aeiou]/

Ranges keep classes short:

/[0-9]/
/[a-z]/
/[A-Fa-f0-9]/

The last pattern matches one hexadecimal character.

Put ^ first to negate a class:

/[^0-9]/

This matches one character that is not an ASCII digit. It does not mean "the whole string contains no digits." For that, anchor and repeat the class:

/^[^0-9]*$/

Shorthand character classes

JavaScript provides common character classes as shorthands:

PatternMatches
\dAn ASCII digit, the same as [0-9]
\DAnything except an ASCII digit
\wAn ASCII letter, digit, or underscore
\WAnything except an ASCII letter, digit, or underscore
\sA whitespace character
\SA non-whitespace character
\tA tab
\rA carriage return
\nA line feed

Without Unicode-aware case folding, \w is the same as [A-Za-z0-9_]. It does not match every letter in every language. Use a Unicode property escape when you need that:

console.log(/^\p{Letter}+$/u.test('München')) // true

Unicode property escapes require the u or v flag.

Anchors and boundaries

Anchors match positions. They do not consume characters.

PatternPosition
^Start of the string, or a line with the m flag
$End of the string, or a line with the m flag
\bBetween word and non-word characters, including an edge next to a word character
\BAny position that is not a word boundary

Use both anchors to match an entire string:

console.log(/^cat$/.test('cat')) // true
console.log(/^cat$/.test('a cat')) // false

Use word boundaries to avoid partial words:

console.log(/\bcat\b/.test('a cat naps')) // true
console.log(/\bcat\b/.test('scatter')) // false

Word boundaries use the same mostly ASCII idea of a word as \w. They can be surprising with non-Latin text.

Quantifiers

A quantifier repeats the token immediately before it.

QuantifierMeaning
?Zero or one
*Zero or more
+One or more
{3}Exactly three
{3,}Three or more
{3,5}Between three and five

The question mark makes the preceding token optional:

/colou?r/

It matches color and colour.

The star allows zero matches:

console.log(/^\d*$/.test('')) // true
console.log(/^\d*$/.test('123')) // true

The plus requires at least one:

console.log(/^\d+$/.test('')) // false
console.log(/^\d+$/.test('123')) // true

Quantifiers apply to one token. /hello*/ means hell followed by zero or more o characters. Use a group to repeat the whole word: /(?:hello)*/.

Greedy and lazy matching

Quantifiers are greedy by default. They take as much text as possible:

'<b>one</b><i>two</i>'.match(/<.+>/)
// ['<b>one</b><i>two</i>']

Add ? after a quantifier to make it lazy:

'<b>one</b><i>two</i>'.match(/<.+?>/g)
// ['<b>', '</b>', '<i>', '</i>']

Lazy does not mean "safe HTML parser." Use the DOM when you need to understand HTML structure.

Alternation and groups

Alternation |

The pipe means "or":

/cat|dog/

Alternation has low precedence. Group it when anchors or quantifiers should apply to every option:

console.log(/^(?:cat|dog)$/.test('dog')) // true
console.log(/^(?:cat|dog)$/.test('hotdog')) // false

Capturing groups ()

Parentheses group a pattern and capture its match:

const match = '2026-08-29'.match(/(\d{4})-(\d{2})-(\d{2})/)
 
console.log(match?.[0]) // 2026-08-29
console.log(match?.[1]) // 2026
console.log(match?.[2]) // 08
console.log(match?.[3]) // 29

Captured values can be used in replacements:

const date = '2026-08-29'
 
console.log(date.replace(/(\d{4})-(\d{2})-(\d{2})/, '$3/$2/$1'))
// 29/08/2026

Non-capturing groups (?:)

Use a non-capturing group when you only need grouping:

/^(?:cat|dog)$/

It keeps the result free of captures you do not use. This also makes the pattern a bit faster.

Named capturing groups

Names make larger patterns easier to read:

const match = '2026-08-29'.match(/(?<year>\d{4})-(?<month>\d{2})-(?<day>\d{2})/)
 
console.log(match?.groups)
// { year: '2026', month: '08', day: '29' }

Backreferences

A backreference matches the same text as an earlier group. \1 refers to the first capture:

console.log(/\b(\w+)\s+\1\b/i.test('the the')) // true
console.log(/\b(\w+)\s+\1\b/i.test('the cat')) // false

Named groups use \k<name>:

console.log(/(?<quote>['"]).*?\k<quote>/.test('Say "hello"')) // true

Lookahead and lookbehind

Lookarounds require surrounding text without including it in the match.

PatternMeaning
x(?=y)Match x followed by y
x(?!y)Match x not followed by y
(?<=y)xMatch x preceded by y
(?<!y)xMatch x not preceded by y

For example, match the number after a dollar sign without matching the sign:

'Total: $42'.match(/(?<=\$)\d+/)?.[0]
// 42

Lookbehind is less portable in old JavaScript runtimes. A capture often does the same job:

'Total: $42'.match(/\$(\d+)/)?.[1]
// 42

Read more: Match text between two markers with regex.

Regex flags

Flags change how a pattern runs. Put them after a literal or pass them as the second constructor argument.

FlagNameEffect
dIndicesAdds start and end indices for matches and captures
gGlobalFinds successive matches and updates lastIndex
iIgnore caseMatches without case sensitivity
mMultilineMakes ^ and $ work at line boundaries
sDot allLets . match line terminators
uUnicodeUses Unicode-aware parsing and code points
vUnicode setsAdds richer Unicode-aware character classes
yStickyMatches only at lastIndex

The u and v flags cannot be used together.

Common combinations are /word/gi for every case-insensitive match and /^value$/m for whole lines.

The g flag does not always mean "return every match." Each string method has its own return shape. The next section shows the difference.

Using regex with JavaScript

Check a match with .test()

RegExp.prototype.test() returns a boolean:

const isSecureUrl = /^https:\/\//.test('https://example.com')
 
console.log(isSecureUrl) // true

Avoid reusing a global or sticky regex for independent test() calls. Those flags update lastIndex, so repeated calls can produce surprising results.

String.prototype.search() returns the first match's index, or -1:

console.log('bad unboxing'.search(/box/)) // 6
console.log('bad unboxing'.search(/duck/)) // -1

Get one match with .match()

Without g, String.prototype.match() returns the full match, captures, and extra metadata:

const match = 'Name: Ada'.match(/Name: (\w+)/)
 
console.log(match?.[0]) // Name: Ada
console.log(match?.[1]) // Ada
console.log(match?.index) // 0

With g, it returns all full matches but drops the captures:

const text = 'Tags: {{first}} and {{second}}'
 
console.log(text.match(/\{\{\w+\}\}/g))
// ['{{first}}', '{{second}}']

It returns null when nothing matches.

Get every match with .matchAll()

String.prototype.matchAll() keeps the captures for every match. The regex must have the g flag:

const text = 'Tags: {{first}} and {{second}}'
const matches = text.matchAll(/\{\{(\w+)\}\}/g)
 
console.log([...matches].map(match => match[1]))
// ['first', 'second']

matchAll() returns an iterator. Spread it, use Array.from(), or loop over it with for...of.

Replace matches with .replace()

String.prototype.replace() accepts a replacement string or function:

const text = 'Good morning, morning people'
 
console.log(text.replace(/morning/g, 'evening'))
// Good evening, evening people

The function receives the full match, captures, offset, original string, and named groups:

const values = { fruitCount: 5, veggieCount: 7 }
const text = '{{fruitCount}} fruit and {{veggieCount}} vegetables'
 
const result = text.replace(/\{\{(\w+)\}\}/g, (match, key) => {
  return Object.hasOwn(values, key) ? values[key] : match
})
 
console.log(result)
// 5 fruit and 7 vegetables

Without g, replace() changes only the first match. replaceAll() is another option, but a regex passed to it must also have g.

Split on a pattern with .split()

String.prototype.split() can use a regex as its separator:

const words = 'one, two;three'.split(/[,;]\s*/)
 
console.log(words)
// ['one', 'two', 'three']

If the separator contains capturing groups, their matches are included in the result. Use (?:) when you do not want that.

Read successive matches with .exec()

RegExp.prototype.exec() returns one match at a time. A global or sticky regex stores the next starting position in lastIndex:

const pattern = /\b\w+\b/g
const text = 'one two'
 
console.log(pattern.exec(text)?.[0]) // one
console.log(pattern.lastIndex) // 3
console.log(pattern.exec(text)?.[0]) // two
console.log(pattern.lastIndex) // 7

Prefer matchAll() when you simply need every match. Use exec() when its stateful control is useful.

Using variables safely

This pattern looks for the literal word needle, not the variable:

const needle = 'work'
 
console.log(/needle/.test('Will this work?')) // false

Build a dynamic pattern with RegExp:

const needle = 'work'
const pattern = new RegExp(needle, 'i')
 
pattern.test('Will this work?') // true

Escape dynamic text before treating it as literal text. Otherwise input such as a.b changes the meaning of the pattern:

const needle = 'a.b'
const pattern = new RegExp(RegExp.escape(needle), 'i')
 
pattern.test('Use a.b') // true
pattern.test('Use axb') // false

RegExp.escape() is the built-in choice in current JavaScript. Check runtime support if you target older browsers or Node.js versions.

Practical patterns

Match a whole ASCII word

const word = 'cat'
const pattern = new RegExp(`\\b${RegExp.escape(word)}\\b`, 'i')
 
pattern.test('A cat naps') // true
pattern.test('A scatter plot') // false

Match a CSS hexadecimal color

CSS hex colors can contain three, four, six, or eight hexadecimal digits:

const hexColor = /^#(?:[\da-f]{3,4}|[\da-f]{6}(?:[\da-f]{2})?)$/i
 
hexColor.test('#09f') // true
hexColor.test('#0099ffaa') // true
hexColor.test('#xyz') // false

Match an MD5-shaped string

An MD5 digest is 32 hexadecimal characters:

const md5 = /^[\da-f]{32}$/i
 
md5.test('7e18a1b53182d6124253453811b67eb0') // true

This checks the format. It cannot prove how the value was produced.

Extract placeholders

const text = 'Hello, {{first_name}} {{last_name}}'
const placeholder = /\{\{(?<key>[A-Za-z_]\w*)\}\}/g
 
const keys = [...text.matchAll(placeholder)].map(match => match.groups?.key)
 
console.log(keys)
// ['first_name', 'last_name']

Check a date format

const isoDate =
  /^(?<year>\d{4})-(?<month>0[1-9]|1[0-2])-(?<day>0[1-9]|[12]\d|3[01])$/
 
isoDate.test('2026-08-29') // true
isoDate.test('29-08-2026') // false

This checks the shape and broad ranges. It still accepts impossible dates such as 2026-02-31. Use a date API for calendar validation.

Check a known web origin

A regex can check a narrow URL shape:

const siteUrl = /^https:\/\/example\.com(?:[/?#]|$)/i
 
siteUrl.test('https://example.com/docs') // true
siteUrl.test('https://example.com.evil.test') // false

For real URL parsing, use the URL class:

const url = new URL('/docs', 'https://example.com')
const isInternal = url.origin === 'https://example.com'
 
console.log(isInternal) // true

Do not use a giant regex to parse every valid URL, email address, programming language, or HTML document. Use the parser made for that format.

Common mistakes

Forgetting anchors

/\d{4}/ finds four digits anywhere. /^\d{4}$/ requires exactly four digits.

Repeating the wrong token

/hello*/ repeats only o. /(?:hello)*/ repeats hello.

Forgetting to escape a dot

example.com matches exampleXcom. example\.com matches the literal domain.

Adding g everywhere

Flags affect method behavior. match() loses captures with g, and a reused global test() pattern changes its starting position.

Using \w for every language

\w is mostly ASCII. Use Unicode property escapes such as \p{Letter} with u or v when the input is multilingual.

Injecting raw text into RegExp

Dynamic text may contain metacharacters. Pass it through RegExp.escape() when it should be literal.

Solving structure with a text pattern

Regex is excellent for local text patterns. It is a poor substitute for an HTML, URL, JSON, or language parser.

Quick reference

SyntaxMeaning
.Any character except a line terminator, unless s is used
[abc]One of a, b, or c
[^abc]One character except a, b, or c
\d / \DDigit / non-digit
\w / \WASCII word / non-word character
\s / \SWhitespace / non-whitespace
^ / $Start / end anchor
\b / \BWord boundary / non-boundary
x?Zero or one x
x*Zero or more x
x+One or more x
x{2,4}Two to four x characters
x|yx or y
(x)Capture x
(?:x)Group x without capturing
(?<name>x)Capture x as name
\1Repeat the first capture
x(?=y)x followed by y
(?<=y)xx preceded by y

Testing regex

Test patterns against examples that should match and examples that should not. regex101 is useful, but select its JavaScript flavor. For exact runtime behavior, write a small JavaScript test.

Keep patterns narrow. Name complex pieces in prose or code. If a regex takes longer to explain than the format itself, a parser may be clearer.

For more detail, see the MDN regular expressions guide and the MDN regex reference.