This JavaScript regex guide connects pattern syntax to the APIs that actually use it. Many bugs come not from the expression, but from choosing match instead of matchAll, forgetting g, reusing a stateful regex, or double-escaping a dynamic pattern.
Create a RegExp Correctly
Use a literal for a static pattern:
const ticketPattern = /^TKT-[0-9]+$/u;
Use the constructor when part of the pattern is dynamic:
const prefix = "TKT";
const ticketPattern = new RegExp(`^${prefix}-[0-9]+$`, "u");
Dynamic literal text must be escaped before interpolation. In runtimes with RegExp.escape, follow that runtime's documentation; otherwise use a well-reviewed escaping helper rather than a casual replacement snippet.
The constructor also adds JavaScript string escaping. A regex-level d+ becomes:
const fromLiteral = /d+/u;
const fromConstructor = new RegExp("\d+", "u");
JavaScript Regex Flags
| Flag | Purpose |
|---|---|
d |
Provide match indices |
g |
Global search and stateful iteration |
i |
Case-insensitive matching |
m |
Make anchors apply at line boundaries |
s |
Let dot match line terminators |
u |
Unicode-aware matching |
v |
Unicode sets mode with additional capabilities |
y |
Sticky matching at lastIndex |
Feature support depends on the deployed JavaScript runtime. Transpilation does not automatically reproduce every regex feature in an older engine.
Choose the Right API
test() for a yes/no answer
const isSlug = /^[a-z0-9]+(?:-[a-z0-9]+)*$/u.test("regex-guide");
exec() for one match and its captures
const match = /^(?<key>[a-z]+)=(?<value>.*)$/u.exec("mode=prod");
console.log(match?.groups);
matchAll() for all matches with captures
const input = "TKT-12, TKT-93";
const tickets = [...input.matchAll(/TKT-(?<id>[0-9]+)/gu)]
.map((match) => match.groups.id);
matchAll() expects a global regex when passed a RegExp.
replace() for transformation
const output = "Doe, Jane".replace(
/^(?<last>[^,]+),s*(?<first>.+)$/u,
"$<first> $<last>"
);
Use a callback for conditional work:
const normalized = "item-007".replace(/[0-9]+/u, (digits) => String(Number(digits)));
split() for controlled delimiters
const values = "alpha, beta;gamma".split(/s*[,;]s*/u);
Capturing parentheses in a split regex may include delimiters in the result. Use (?:...) when capture is not intended.
The Stateful g and y Trap
test() and exec() update lastIndex on global or sticky regex objects:
const word = /[a-z]+/g;
console.log(word.test("alpha")); // true
console.log(word.test("alpha")); // false: search resumes at lastIndex
Avoid shared state by removing g for a boolean test, creating a new regex, or deliberately resetting lastIndex.
Unicode and User-Visible Text
Use Unicode-aware modes intentionally. Property escapes can describe broad categories:
const lettersOnly = /^p{Letter}+$/u;
Unicode letters, code points, code units, and user-perceived characters are not interchangeable. Complex length or normalization rules belong in application logic, often after String.prototype.normalize() under a defined policy.
Validation Example
Validate the whole input and keep semantic checks separate:
const dateShape = /^(?<year>[0-9]{4})-(?<month>[0-9]{2})-(?<day>[0-9]{2})$/u;
function parseIsoDate(value) {
const match = dateShape.exec(value);
if (!match) return null;
const { year, month, day } = match.groups;
const date = new Date(`${year}-${month}-${day}T00:00:00Z`);
return date.getUTCFullYear() === Number(year) &&
date.getUTCMonth() + 1 === Number(month) &&
date.getUTCDate() === Number(day)
? date
: null;
}
Regex checks shape; date logic checks reality.
Common JavaScript Regex Errors
- Missing
gwhen every occurrence is required. - Keeping
gon a reused boolean tester. - Writing constructor strings as though they were literals.
- Assuming dot matches newlines without
s. - Using
^and$withmduring whole-input validation. - Deploying modern lookbehind or Unicode syntax to an unsupported runtime.
- Interpolating user text as executable regex syntax.
Testing and Production Practices
Use a table-driven test in the project's test framework:
const pattern = /^[a-z0-9]+(?:-[a-z0-9]+)*$/u;
for (const [input, expected] of [
["regex-guide", true],
["Regex-Guide", false],
["regex--guide", false],
["", false],
]) {
console.assert(pattern.test(input) === expected, input);
}
For untrusted input, impose length limits, avoid ambiguous nested repetition, and benchmark long near-matches. Regex validation is not output encoding and does not replace parameterized queries or context-aware escaping.
Conclusion
A dependable JavaScript regex guide must include the surrounding API. Choose literals or constructors deliberately, understand g and lastIndex, enable Unicode modes according to requirements, and test captures and transformations—not only boolean matches.
FAQ
Should I use a regex literal or new RegExp()?
Use a literal for static patterns and the constructor when the pattern is assembled at runtime.
Why does test() alternate between true and false?
A reused regex with g or y updates lastIndex between calls.
What is the difference between u and v?
Both are Unicode-aware modes; v adds newer Unicode-set capabilities. Check target-runtime support and MDN details.
Does match() return capture groups for every global match?
With g, match() normally returns full matches rather than every capture. Use matchAll() when captures are needed per match.
Can regex sanitize HTML?
No. Use context-aware output encoding and established sanitization libraries.
Suggested Internal Links
- Regex Tutorial for Beginners
- Regex Groups and Backreferences
- Best Online Regex Testers
- Regex Performance and ReDoS