How to Escape Special Characters in JavaScript Regular Expressions
Regular expressions use certain characters as operators with special meanings. The dot matches any character, the asterisk means "zero or more," parentheses create groups, and brackets define character sets. But what happens when you need to match these characters literally? If you want to find a dot in a filename, a dollar sign in a price, or a question mark in a sentence, you must escape these special characters so the regex engine treats them as literal characters rather than operators.
Escaping is straightforward in concept but is one of the most common sources of regex bugs, especially when building patterns dynamically with the RegExp constructor, where JavaScript string escaping and regex escaping interact. This guide covers exactly which characters need escaping, how to escape them in both regex syntaxes, and how to safely handle user input in dynamic patterns.
Characters That Need Escaping
Regular expressions reserve a set of characters as metacharacters, each with a special function in the pattern language. To match any of these characters literally, you must precede them with a backslash (\).
The Complete List
\ ^ $ . | ? * + ( ) [ ] { }
These 14 characters have special meaning in regex patterns. Here is what each one does and why it needs escaping:
| Character | Special Meaning | Escaped Form | Matches Literally |
|---|---|---|---|
\ | Escape character itself | \\ | A backslash |
^ | Start of string anchor | \^ | A caret |
$ | End of string anchor | \$ | A dollar sign |
. | Any character (except newline) | \. | A period/dot |
| ` | ` | Alternation (OR) | | |
? | Zero or one quantifier | \? | A question mark |
* | Zero or more quantifier | \* | An asterisk |
+ | One or more quantifier | \+ | A plus sign |
( | Start of group | \( | An opening parenthesis |
) | End of group | \) | A closing parenthesis |
[ | Start of character class | \[ | An opening bracket |
] | End of character class | \] | A closing bracket |
{ | Start of quantifier | \{ | An opening brace |
} | End of quantifier | \} | A closing brace |
Escaping in Practice
Let's see each character unescaped (broken) and escaped (correct):
The dot .:
// ❌ Without escaping: . matches ANY character
console.log(/1.1/.test('1.1')); // true
console.log(/1.1/.test('1X1')); // true (probably not intended!)
console.log(/1.1/.test('111')); // true (also not intended!)
// ✅ With escaping: \. matches only a literal dot
console.log(/1\.1/.test('1.1')); // true
console.log(/1\.1/.test('1X1')); // false (correctly rejected)
console.log(/1\.1/.test('111')); // false (correctly rejected)
The dollar sign $:
// ❌ Without escaping: $ is the end-of-string anchor
console.log(/price: $100/.test('price: $100')); // false!
// The $ anchors to the end, so it looks for "price: " at end then "100"
// ✅ With escaping: \$ matches a literal dollar sign
console.log(/price: \$100/.test('price: $100')); // true
The asterisk * and plus +:
// ❌ Without escaping: * means "zero or more of preceding"
// /a*b/ means zero or more "a" followed by "b"
console.log(/a*b/.test('b')); // true (zero "a"s)
console.log(/a*b/.test('aaab')); // true
// ✅ With escaping: \* matches a literal asterisk
console.log(/a\*b/.test('a*b')); // true
console.log(/a\*b/.test('aab')); // false
// ❌ Without escaping: + means "one or more of preceding"
console.log(/a+b/.test('aab')); // true
console.log(/a+b/.test('a+b')); // true (because + matches "a" one or more times)
// ✅ With escaping: \+ matches a literal plus
console.log(/a\+b/.test('a+b')); // true
console.log(/a\+b/.test('aab')); // false
Parentheses ( and ):
// ❌ Without escaping: () creates a capturing group
console.log('(hello)'.match(/(hello)/));
// Matches "hello" as a group, not the literal parentheses
// ✅ With escaping: \( and \) match literal parentheses
console.log('(hello)'.match(/\(hello\)/));
// ["(hello)"] (matches the parentheses literally)
The pipe |:
// ❌ Without escaping: | means OR (alternation)
console.log(/yes|no/.test('yes')); // true
console.log(/yes|no/.test('no')); // true
console.log(/yes|no/.test('|')); // false (| is not matched literally)
// ✅ With escaping: \| matches a literal pipe
console.log(/yes\|no/.test('yes|no')); // true
console.log(/yes\|no/.test('yes')); // false
Square brackets [ and ]:
// ❌ Without escaping: [] defines a character class
console.log(/[abc]/.test('a')); // true (matches a, b, or c)
// ✅ With escaping: \[ and \] match literal brackets
console.log(/\[abc\]/.test('[abc]')); // true
console.log(/\[abc\]/.test('a')); // false
Curly braces { and }:
// ❌ Without escaping: {n,m} is a quantifier
console.log(/a{2,3}/.test('aaa')); // true (matches 2-3 "a"s)
// ✅ With escaping: \{ and \} match literal braces
console.log(/a\{2,3\}/.test('a{2,3}')); // true
console.log(/a\{2,3\}/.test('aaa')); // false
The caret ^:
// ❌ Without escaping at start: ^ anchors to start of string
console.log(/^hello/.test('hello world')); // true
console.log(/^hello/.test('say hello')); // false (not at start)
// ✅ With escaping: \^ matches a literal caret
console.log(/\^hello/.test('^hello')); // true
console.log(/\^hello/.test('hello')); // false
The question mark ?:
// ❌ Without escaping: ? makes preceding optional
console.log(/colou?r/.test('color')); // true (u is optional)
console.log(/colou?r/.test('colour')); // true
// ✅ With escaping: \? matches a literal question mark
console.log(/really\?/.test('really?')); // true
console.log(/really\?/.test('really')); // false
The backslash \ itself:
// The backslash is the escape character, so to match a literal backslash,
// you need to escape the escape: \\
console.log(/\\/.test('hello\\world')); // true (matches a backslash)
console.log(/C:\\Users/.test('C:\\Users')); // true (matches "C:\Users")
Real-World Escaping Examples
Matching a file path:
// Windows file path: C:\Users\Documents\file.txt
const path = 'C:\\Users\\Documents\\file.txt';
// Need to escape: backslashes and dot
const regex = /C:\\Users\\Documents\\file\.txt/;
console.log(regex.test(path)); // true
Matching a URL:
// URL contains dots, slashes, question marks, and more
const url = 'https://example.com/path?key=value&other=123';
const regex = /https:\/\/example\.com\/path\?key=value&other=123/;
console.log(regex.test(url)); // true
Note that the forward slash / is not a regex metacharacter, but in literal syntax it needs to be escaped because it is the regex delimiter:
// In literal syntax: / must be escaped because it ends the regex
const regex = /https:\/\/example\.com/;
// In constructor syntax: / does NOT need escaping
const regex2 = new RegExp('https://example\\.com');
// Both work the same way
Matching a price:
const text = 'The price is $29.99 (USD)';
// Escape: $, ., (, )
const regex = /\$\d+\.\d{2} \(USD\)/;
console.log(regex.test(text)); // true
Matching a mathematical expression:
const expr = '(a + b) * c = d';
// Escape: (, ), +, *, =
// Note: = is NOT a regex metacharacter, no escaping needed
const regex = /\(a \+ b\) \* c = d/;
console.log(regex.test(expr)); // true
Characters That Do NOT Need Escaping
Many characters that look like they might be special are actually safe to use without escaping:
// These do NOT need escaping outside of character classes:
// @ # % & - = _ , ; : ' " / < > ~ `
console.log(/@/.test('user@email.com')); // true (@ is not special)
console.log(/#/.test('#hashtag')); // true (# is not special)
console.log(/-/.test('hello-world')); // true (hyphen not special outside [])
console.log(/=/.test('a=b')); // true (= is not special)
console.log(/:/.test('key: value')); // true (: is not special)
console.log(/,/.test('a, b, c')); // true (, is not special)
The forward slash / is not a regex metacharacter. It only needs escaping in literal syntax (/pattern/) because it serves as the regex delimiter. In the RegExp constructor, it does not need escaping at all.
// Literal syntax: must escape /
const regex1 = /http:\/\//;
// Constructor syntax: / is just a regular character
const regex2 = new RegExp('http://');
// Both match the same thing
console.log(regex1.test('http://example.com')); // true
console.log(regex2.test('http://example.com')); // true
Special Rules Inside Character Classes [...]
Inside character classes (square brackets), the rules change. Most metacharacters lose their special meaning and can be used without escaping:
// Inside [...], these are NOT special and don't need escaping:
// $ . | ? * + ( ) { }
console.log(/[.$+*?]/.test('$')); // true ($ is just a literal inside [])
console.log(/[.$+*?]/.test('.')); // true (. is just a literal inside [])
console.log(/[.$+*?]/.test('a')); // false
// Inside [...], these ARE special and DO need escaping:
// \ (still the escape character)
// ] (closes the character class)
// ^ (negates the class (when first))
// - (defines a range (when between characters))
// Escaping inside character classes:
console.log(/[\\\]\^-]/.test('\\')); // true (escaped backslash)
console.log(/[\\\]\^-]/.test(']')); // true (escaped bracket)
console.log(/[\\\]\^-]/.test('^')); // true (escaped caret)
console.log(/[\\\]\^-]/.test('-')); // true (hyphen at end (or escaped))
Handling the hyphen inside character classes:
// Hyphen is special inside [] when between characters (defines a range)
// [a-z] means a through z
// To match a literal hyphen inside [], place it at the start or end:
console.log(/[-abc]/.test('-')); // true (hyphen at start)
console.log(/[abc-]/.test('-')); // true (hyphen at end)
// Or escape it:
console.log(/[a\-z]/.test('-')); // true (escaped hyphen)
Escaping in new RegExp() (Double Backslash)
When using the RegExp constructor, the pattern is passed as a string. Strings in JavaScript have their own escape sequences using backslashes. This means the backslash is processed twice: first by the JavaScript string parser, then by the regex engine. This double processing is the source of most escaping confusion.
The Double Escaping Problem
In a JavaScript string, \ is the escape character for string-level escapes like \n (newline), \t (tab), and \\ (literal backslash). When the string parser sees a backslash, it interprets it as a string escape.
// What the JavaScript string parser does:
console.log('\d'); // "d" (\d is not a recognized string escape, )
// so JS just returns "d"
console.log('\\d'); // "\d" (\\ is a literal backslash, followed by "d")
console.log('\\.'); // "\." (wait, is this right?)
// Let me be precise:
console.log('\d'); // "d" (in non-strict mode, \d → d)
console.log('\\d'); // "\d" (two chars: backslash, d)
When you pass a string to new RegExp(), the regex engine receives whatever the string parser produces. If the string parser consumed the backslash, the regex engine never sees it:
// What the regex engine receives:
// ❌ Single backslash: string parser consumes it
const wrong = new RegExp('\d+');
// String parser: '\d' → 'd' (or just 'd')
// Regex engine receives: /d+/
// This matches one or more literal "d" characters, NOT digits!
console.log(wrong.test('ddd')); // true (matches "d"s)
console.log(wrong.test('123')); // false (doesn't match digits!)
// ✅ Double backslash: string parser produces a literal backslash
const correct = new RegExp('\\d+');
// String parser: '\\d' → '\d' (backslash + d)
// Regex engine receives: /\d+/
// This correctly matches one or more digits
console.log(correct.test('ddd')); // false
console.log(correct.test('123')); // true
The Rule
Every backslash in a regex pattern must be doubled when using the RegExp constructor.
Here is a systematic comparison:
// Literal syntax → Constructor syntax
/\d+/ → new RegExp('\\d+')
/\w+/ → new RegExp('\\w+')
/\s+/ → new RegExp('\\s+')
/\bword\b/ → new RegExp('\\bword\\b')
/\./ → new RegExp('\\.')
/\$/ → new RegExp('\\$')
/\\/ → new RegExp('\\\\')
/\(.*\)/ → new RegExp('\\(.*\\)')
/\d{3}-\d{4}/ → new RegExp('\\d{3}-\\d{4}')
/^hello$/ → new RegExp('^hello$')
/\p{Letter}/u → new RegExp('\\p{Letter}', 'u')
Notice that characters that are not preceded by a backslash (like ^, $, *, +, ., (, )) do not need doubling, because the string parser does not process them:
// These characters are NOT string escape sequences,
// so they pass through the string parser unchanged:
new RegExp('^hello$') // ^ and $ pass through fine
new RegExp('a+b*c?') // +, *, ? pass through fine
new RegExp('(a|b)') // (, ), | pass through fine
new RegExp('[abc]') // [, ] pass through fine
But any backslash in the pattern must be doubled:
// Every \x in the pattern becomes \\x in the string
new RegExp('\\d') // Matches digit
new RegExp('\\w') // Matches word char
new RegExp('\\s') // Matches whitespace
new RegExp('\\b') // Word boundary
new RegExp('\\.') // Literal dot
new RegExp('\\*') // Literal asterisk
new RegExp('\\(') // Literal parenthesis
new RegExp('\\\\') // Literal backslash
new RegExp('\\n') // Matches newline character
new RegExp('\\t') // Matches tab character
Matching a Literal Backslash
Matching a literal backslash is where escaping gets most confusing, because you need escaping at both levels:
// To match a single literal backslash \
// Literal syntax: \\ (one escape for regex)
const regex1 = /\\/;
// Constructor syntax: \\\\ (double escape: \\\\ → string "\\", regex sees \\)
const regex2 = new RegExp('\\\\');
// Both match a single backslash
const text = 'C:\\Users';
console.log(regex1.test(text)); // true
console.log(regex2.test(text)); // true
// Why four backslashes in the constructor?
// '\\\\' → string parser produces '\\' (two characters: \ and \)
// regex engine sees \\ → means "literal backslash"
Let's trace through step by step:
// Goal: match the string "C:\Users"
// The regex needs to match: C, :, \, U, s, e, r, s
// The literal \ requires regex escape: C:\\Users
// Literal syntax:
const regex = /C:\\Users/;
// Constructor syntax:
// Regex needs: C:\\Users
// In a string, each \ must be doubled:
// C → C
// : → :
// \\ → \\\\ (each \ becomes \\)
// U → U, etc.
const regex2 = new RegExp('C:\\\\Users');
console.log(regex.test('C:\\Users')); // true
console.log(regex2.test('C:\\Users')); // true
Converting Literal Syntax to Constructor Syntax
When converting from literal to constructor, follow this process:
- Take the pattern between the slashes
- Double every backslash
- Remove the slashes and pass as a string
- Pass flags as the second argument
// Literal: /^\d{3}-\d{4}$/i
// Step 1: ^\d{3}-\d{4}$ (pattern between slashes)
// Step 2: ^\\d{3}-\\d{4}$ (double backslashes)
// Constructor: new RegExp('^\\d{3}-\\d{4}$', 'i')
const literal = /^\d{3}-\d{4}$/i;
const constructor = new RegExp('^\\d{3}-\\d{4}$', 'i');
console.log(literal.test('555-1234')); // true
console.log(constructor.test('555-1234')); // true
// More examples:
// /\b\w+@\w+\.\w+\b/g
const email = new RegExp('\\b\\w+@\\w+\\.\\w+\\b', 'g');
// /https?:\/\/\S+/g
// note: / doesn't need doubling (not a string escape)
const url = new RegExp('https?://\\S+', 'g');
// /\p{Letter}+/gu
const letters = new RegExp('\\p{Letter}+', 'gu');
Debugging Double Escape Issues
A common debugging technique is to log the string to see what the regex engine actually receives:
// If your regex isn't working, print the string to check:
const pattern1 = '\d+';
console.log('Pattern 1:', pattern1); // "d+" (wrong! Backslash was consumed)
const pattern2 = '\\d+';
console.log('Pattern 2:', pattern2); // "\d+" (correct!)
const pattern3 = '\\\\';
console.log('Pattern 3:', pattern3); // "\\" (regex will match one backslash)
const pattern4 = '\\.';
console.log('Pattern 4:', pattern4); // "\." (wait, is this right?)
// Actually in non-strict mode: '\.' might become '.'
// because \. isn't a recognized string escape
// But this behavior is inconsistent, always use '\\.'
const pattern5 = '\\.';
console.log('Pattern 5:', pattern5); // "." in some engines
const pattern6 = '\\\\.';
console.log('Pattern 6:', pattern6); // "\." (this is reliable)
Do not rely on JavaScript's lenient handling of unrecognized string escapes (like '\.' producing "."). In strict mode, some of these will throw errors. Always double every backslash in RegExp constructor strings, even if it seems to work without doubling.
'use strict';
// Some unrecognized escapes may throw in strict mode
// Always use \\\\ for a regex \\, \\d for \d, etc.
The Escaping Utility Function
The most important practical application of escaping knowledge is the function that escapes arbitrary strings for safe use in regular expressions:
function escapeRegExp(string) {
return string.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
}
This function takes any string and escapes all regex metacharacters, making it safe to use inside a new RegExp() call.
// Without escaping user input:
const userInput = 'price: $9.99 (USD)';
// ❌ Dangerous: special chars are interpreted as regex operators
const broken = new RegExp(userInput);
// Creates: /price: $9.99 (USD)/
// $9 is a backreference, .99 matches any char + "99", () is a group
// This regex won't match what the user expects
// ✅ Safe: special chars are escaped
const safe = new RegExp(escapeRegExp(userInput));
// Creates: /price: \$9\.99 \(USD\)/
// Matches the literal string "price: $9.99 (USD)"
console.log(safe.test('price: $9.99 (USD)')); // true
How the escaping function works:
// /[.*+?^${}()|[\]\\]/g (matches any special regex character)
// '\\$&' (replaces with a backslash + the matched character itself)
// $& is a special replacement pattern meaning "the entire match"
console.log(escapeRegExp('hello.world')); // "hello\\.world"
console.log(escapeRegExp('(a+b)*c')); // "\\(a\\+b\\)\\*c"
console.log(escapeRegExp('file[0].txt')); // "file\\[0\\]\\.txt"
console.log(escapeRegExp('C:\\Users\\file')); // "C:\\\\Users\\\\file"
console.log(escapeRegExp('$100')); // "\\$100"
console.log(escapeRegExp('normal text')); // "normal text" (no change)
Using the Escape Function in Practice
Dynamic search with word boundaries:
function searchWholeWord(text, word) {
const escaped = escapeRegExp(word);
const regex = new RegExp(`\\b${escaped}\\b`, 'gi');
return text.match(regex) || [];
}
console.log(searchWholeWord('C++ and C# are languages', 'C++'));
// ["C++"] (the ++ is escaped, not treated as quantifiers)
console.log(searchWholeWord('Price is $9.99 today', '$9.99'));
// ["$9.99"] ($ and . are escaped)
Dynamic search and replace:
function replaceAll(text, search, replacement) {
const escaped = escapeRegExp(search);
const regex = new RegExp(escaped, 'g');
return text.replace(regex, replacement);
}
console.log(replaceAll('a.b.c.d', '.', '-'));
// "a-b-c-d" (dots are literal, not "any character")
console.log(replaceAll('1+2+3', '+', ' plus '));
// "1 plus 2 plus 3"
console.log(replaceAll('(hello) (world)', '(', '['));
// "[hello) [world)"
Building complex patterns from user input:
function createSearchPattern(terms, options = {}) {
const { wholeWord = false, caseSensitive = false } = options;
const escapedTerms = terms.map(escapeRegExp);
let pattern = escapedTerms.join('|');
if (wholeWord) {
pattern = `\\b(?:${pattern})\\b`;
}
const flags = 'g' + (caseSensitive ? '' : 'i');
return new RegExp(pattern, flags);
}
const regex = createSearchPattern(['$100', 'C++', 'file.txt'], { wholeWord: true });
console.log(regex);
// /\b(?:\$100|C\+\+|file\.txt)\b/gi
const text = 'The price is $100 for C++ and file.txt';
console.log(text.match(regex));
// ["$100", "C++", "file.txt"]
Quick Reference: Escaping Cheat Sheet
Literal Syntax Constructor Syntax Matches
───────────────────── ───────────────────── ───────────────
/\d/ new RegExp('\\d') digit
/\w/ new RegExp('\\w') word character
/\s/ new RegExp('\\s') whitespace
/\b/ new RegExp('\\b') word boundary
/\./ new RegExp('\\.') literal dot
/\$/ new RegExp('\\$') literal $
/\+/ new RegExp('\\+') literal +
/\*/ new RegExp('\\*') literal *
/\?/ new RegExp('\\?') literal ?
/\^/ new RegExp('\\^') literal ^
/\|/ new RegExp('\\|') literal |
/\(/ new RegExp('\\(') literal (
/\)/ new RegExp('\\)') literal )
/\[/ new RegExp('\\[') literal [
/\]/ new RegExp('\\]') literal ]
/\{/ new RegExp('\\{') literal {
/\}/ new RegExp('\\}') literal }
/\\/ new RegExp('\\\\') literal \
/\// new RegExp('/') literal / (no escape needed in constructor)
Summary
Escaping special characters correctly is fundamental to writing regex patterns that work as intended:
- 14 metacharacters need escaping to match literally:
\ ^ $ . | ? * + ( ) [ ] { }. Precede each with a backslash:\.matches a dot,\$matches a dollar sign,\\matches a backslash. - Inside character classes
[...], most metacharacters lose their special meaning. Only\,],^(at start), and-(between characters) need escaping inside brackets. - The forward slash
/is not a regex metacharacter. It only needs escaping in literal syntax (/pattern/) because it is the delimiter. In theRegExpconstructor, it is just a regular character. - In the
RegExpconstructor, every backslash must be doubled because the JavaScript string parser processes backslashes first./\d+/becomesnew RegExp('\\d+'). A literal backslash/\\/becomesnew RegExp('\\\\'). - Always use the
escapeRegExpfunction when building patterns from user input or dynamic strings. It prevents metacharacters from being interpreted as regex operators, avoiding both bugs and potential ReDoS attacks.