Invalid Regular Expression: Every JavaScript Syntax Error and How to Fix It
new RegExp(pattern) and regex literals both throw a SyntaxError the moment a pattern can't be parsed at all — before it ever gets the chance to match or fail to match anything. The message always starts the same way, Invalid regular expression: /.../: , followed by a short, specific reason. That reason is usually the fastest route to the fix, but it's also easy to misread if you've never seen it before. This covers seven real causes, the exact V8 message each one produces, and a live, prefilled Regex Lab example for each so you can watch it fail on the exact pattern instead of a description of one.
+, ., *, (, ), [, ], {, }, \, |, ^, $ — as if it were already a valid pattern, most commonly by passing raw user input or a copy-pasted snippet straight into new RegExp() without escaping it first.
Quick reference
All seven results below were verified live in Regex Lab against a real build — each pattern constructed with Node's new RegExp() first to confirm the exact message, then reproduced in the browser via the same V8 engine Chromium ships.
| Message | Most likely cause | Fix |
|---|---|---|
| Nothing to repeat | A quantifier (+ * ? {n}) with nothing before it to repeat | Escape the leading/duplicate metacharacter, or remove it |
| Unterminated group | An opening ( with no matching ) | Close the group, or escape the paren if you meant it literally |
| Unmatched ')' | A closing ) with no opening ( | Escape it as \) if you meant a literal parenthesis |
| Unterminated character class | An opening [ with no matching ] | Close the class, or escape a literal [ |
| Invalid group | A (?...) sequence JS doesn't recognize (often ported from Python) | Use JS syntax — (?<name>...), not (?P<name>...) |
| numbers out of order in {} quantifier | A {min,max} quantifier where min > max | Swap the two numbers |
| \ at end of pattern | A trailing, unescaped backslash (often a pasted Windows path) | Escape it as \\\\, or strip the trailing slash |
1. Nothing to repeat
Quantifiers (+, *, ?, {n,m}) apply to whatever came immediately before them in the pattern. If that's the very first character, or another quantifier, there's nothing for it to repeat. This is the single most common way raw user input breaks a dynamically-built regex, because + is a completely ordinary character in plain text (a phone number's country-code prefix) and an extremely meaningful one in a pattern.
const phoneSearch = '+15555550123'; // pasted from a CSV export
new RegExp(phoneSearch);
// SyntaxError: Invalid regular expression: /+15555550123/: Nothing to repeat
The same error also shows up from a doubled quantifier — typically a copy-paste artifact, or someone trying to "make a wildcard more greedy" by repeating it, which isn't valid syntax:
const pattern = 'sales_report' + '*' + '*'; // meant "match anything", doubled by mistake
new RegExp(pattern);
// SyntaxError: Invalid regular expression: /sales_report**/: Nothing to repeat
Fix either case by escaping the string before it becomes part of a pattern, using the standard one-line escape helper (there's no built-in for this): str.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'). For the doubled-quantifier case specifically, a single * already means "zero or more" — delete the extra one.
See it fail live
A customer phone number, used directly as a search pattern instead of as literal text to find:
Open this example in Regex Lab2. Unterminated group
Every ( needs a matching ) somewhere later in the pattern. This shows up constantly when someone wants to search log lines for literal text that happens to contain a parenthesis — typing the text as-is, rather than as an escaped literal, leaves the group open with nothing to close it.
const logFilter = '(unauthorized'; // meant to match the literal text "(unauthorized)"
new RegExp(logFilter, 'i');
// SyntaxError: Invalid regular expression: /(unauthorized/i: Unterminated group
If you want to match a literal parenthesis rather than open a capture group, escape it: \(unauthorized\). If you actually meant to open a group, add the closing ).
See it fail live
Searching log output for a bracketed status tag, missing its closing paren:
Open this example in Regex Lab3. Unmatched ')'
The mirror image of the previous error: a closing ) that was never opened. It's just as often the exact same log-search scenario, with the pattern trimmed from the other end.
const logFilter = 'unauthorized)'; // the opening paren got trimmed off
new RegExp(logFilter, 'i');
// SyntaxError: Invalid regular expression: /unauthorized)/i: Unmatched ')'
Same fix as above: escape it as \) for a literal parenthesis, or add the matching ( if a group was actually intended.
See it fail live
The same log search, this time missing its opening paren:
Open this example in Regex Lab4. Unterminated character class
A character class opened with [ needs a closing ]. A common way this slips through: building an "allowed characters" pattern for a username or slug validator by hand, and losing the closing bracket during an edit.
const usernamePattern = '[a-z'; // closing bracket dropped during a refactor
new RegExp(usernamePattern);
// SyntaxError: Invalid regular expression: /[a-z/: Unterminated character class
Add the missing ]: [a-z]. If you meant a literal square bracket rather than a class, escape it instead: \[a-z.
See it fail live
A lowercase-letter character class, missing its closing bracket:
Open this example in Regex Lab5. Invalid group
Any (? sequence has to continue with a syntax JavaScript actually recognizes: (?: for a non-capturing group, (?= / (?! for lookaheads, (?<= / (?<! for lookbehinds, or (?<name> for a named capture group. The most common way this breaks is porting a regex from Python or PCRE, which spell a named group (?P<name>...) — the extra P isn't valid JavaScript syntax at all.
const isoDate = '(?P<year>\\d{4})-(?P<month>\\d{2})-(?P<day>\\d{2})'; // Python syntax
new RegExp(isoDate);
// SyntaxError: Invalid regular expression: /(?P<year>\d{4})-.../: Invalid group
Drop the P — JavaScript's named groups (supported since 2018, no library needed) are written (?<year>\d{4}).
6. numbers out of order in {} quantifier
A {min,max} quantifier requires min ≤ max. This one tends to appear when a pattern is generated from two form fields (a minimum and maximum length, say) and the values end up swapped — the resulting pattern is not just wrong, it's unparseable.
const minLen = 8, maxLen = 4; // swapped by mistake
const pattern = `.{${minLen},${maxLen}}`;
new RegExp(pattern);
// SyntaxError: Invalid regular expression: /.{8,4}/: numbers out of order in {} quantifier
Fix by validating that min ≤ max before building the pattern string, not after the RegExp constructor has already thrown.
See it fail live
A length-range quantifier built from two swapped variables:
Open this example in Regex Lab7. \ at end of pattern
A backslash escapes whatever character comes right after it, so a pattern can never end on an unescaped, unpaired \. This is almost always a pasted Windows file path used directly as a matcher — the trailing backslash before the closing quote has nothing left to escape.
const pathFilter = 'C:\\Users\\dev\\'; // trailing backslash, pasted from Explorer's address bar
new RegExp(pathFilter);
// SyntaxError: Invalid regular expression: /C:\Users\dev\/: \ at end of pattern
Escape every backslash in the literal path before it becomes a pattern (C:\\\\Users\\\\dev\\\\, i.e. each single backslash doubled), or run it through the same generic escape helper from the first section — it handles \ along with every other metacharacter.
See it fail live
A Windows path used as a matcher, with its trailing backslash left unescaped:
Open this example in Regex Lab(?<year>\d{4})-(?<month>\d{2})-(?<year>\d{2}) — throws Duplicate capture group name. It's an easy one to introduce by copy-pasting a group definition twice and forgetting to rename the second copy. See it fail live — rename one of the two year groups to fix it.
Debugging checklist
- If the pattern comes from a variable rather than being typed by hand, ask whether that variable could ever contain
+ . * ( ) [ ] { } \ | ^ $as ordinary data. If yes, escape it before it reachesnew RegExp()— don't wait for the exception. - Read the exact reason after the colon in the error message; it names the specific rule that was broken, not just "your regex is wrong."
- Count parens and brackets by hand for "Unterminated"/"Unmatched" errors — they're almost always one character short or one character extra, not a deeper logic problem.
- If a pattern was copied from Python, PHP, or a man page, check named groups and comments specifically —
(?P<name>and(?#comment)are both invalid JavaScript syntax even though they're valid elsewhere. - For
{min,max}errors, print the two numbers right before they're interpolated into the pattern string — a silent swap upstream is the usual cause.
🔍 Paste your pattern and see exactly where it fails
Regex Lab compiles and tests your pattern entirely in your browser, live as you type, and now shows the exact syntax error message instead of just reporting zero matches. Nothing you paste is ever uploaded.
Open Regex Lab →