Regular expressions are most useful when they solve a narrowly defined text-processing problem. This practical collection of JavaScript regex recipes covers validation, extraction, replacement, capture groups, flags, escaping, lookarounds, and Unicode-aware matching, with patterns you can adapt and test in a real application.
Overview
A JavaScript regular expression is a pattern used to search, validate, split, or transform text. You can create one with a literal such as /\d+/ or with the RegExp constructor when the pattern is dynamic:
const digits = /\d+/;
const dynamic = new RegExp('\\d+');The two forms use different escaping rules. In a regex literal, \d is written directly. In a JavaScript string passed to RegExp, the backslash must itself be escaped, so the same pattern becomes '\\d+'.
Before writing a pattern, define whether you need to match an entire value, find one part inside a larger string, or replace selected text. These are different tasks. For example, test() is convenient for a yes-or-no check, match() can return captured text, and replace() is designed for transformation.
Regex is a good fit for local, well-defined text rules. It is less suitable for parsing nested languages, arbitrary HTML, or complicated business rules that would be clearer as ordinary JavaScript. Keep patterns small, name them, and pair them with tests that document the intended behavior.
What to track
When maintaining JavaScript regular expressions, track more than whether a single example passes. A useful recipe should record the input shape, the expected result, the flags, and the boundaries of the rule.
Common JavaScript regex recipes
| Task | Pattern | Typical use |
|---|---|---|
| Digits only | /^\d+$/ | Validate a nonempty string containing only ASCII digits. |
| Optional decimal number | /^-?\d+(?:\.\d+)?$/ | Match integers and decimal values with an optional minus sign. |
| Trim surrounding whitespace | /^\s+|\s+$/g | Remove leading and trailing whitespace with replace(). |
| Extract hashtags | /#[A-Za-z0-9_]+/g | Find simple Latin-character hashtags in text. |
| Find words | /\b[A-Za-z]+\b/g | Extract basic alphabetic words. |
| Collapse repeated spaces | /\s+/g | Convert runs of whitespace into one space. |
| Simple key-value pair | /^([^=]+)=(.*)$/ | Capture a key and the remaining value around the first separator. |
These examples intentionally describe limited formats. For instance, /^\d+$/ does not validate a number type, locale-specific digits, or a formatted value such as 1,000. State those boundaries in your code or tests instead of allowing a short pattern to imply more than it checks.
Validation with anchors
Anchors determine whether a match must occur at the beginning or end of the input. Use ^ and $ when the complete string must follow a rule:
const postalCode = /^\d{5}$/;
postalCode.test('02139'); // true
postalCode.test('02139-1234'); // falseFor a form field, validate the actual value passed to the rule and decide explicitly how empty input should behave. A pattern such as /^\d*$/ permits an empty string, while /^\d+$/ requires at least one digit. That small distinction prevents many accidental passes.
Capture groups for extraction
Parentheses create capture groups. Use them when you need a part of the match rather than only a boolean result:
const versionPattern = /^v(\d+)\.(\d+)\.(\d+)$/;
const match = 'v3.12.4'.match(versionPattern);
if (match) {
const [, major, minor, patch] = match;
console.log({ major, minor, patch });
}Use noncapturing groups, written as (?:...), when grouping is needed for alternation or quantifiers but the text should not appear in the result:
const filePattern = /\.(?:png|jpg|webp)$/i;Named capture groups can make larger patterns easier to read:
const datePattern = /^(?<year>\d{4})-(?<month>\d{2})-(?<day>\d{2})$/;
const result = '2026-10-30'.match(datePattern);
console.log(result?.groups);Use named groups only when your project targets environments that support them, and keep a simpler positional form if compatibility with older runtimes is required.
Extraction and replacement
The g flag asks many string methods to process all matching occurrences rather than stopping after the first. For example:
const text = 'red, blue, green';
const colors = text.match(/[a-z]+/g); // ['red', 'blue', 'green']For replacement, a string replacement is enough when every match becomes the same value:
const normalized = 'one two\nthree'.replace(/\s+/g, ' ');
// 'one two three'Use a replacement function when the output depends on the captured text:
const input = 'item-12 item-7';
const output = input.replace(/item-(\d+)/g, (_, number) => `item-${number.padStart(3, '0')}`);
// 'item-012 item-007'Flags, escaping, and lookarounds
The most frequently used flags are i for case-insensitive matching, g for global processing, m for multiline anchors, s for allowing a dot to match line terminators, and u for Unicode-aware processing. Select only the flags that describe the requirement; unnecessary flags can make behavior harder to reason about.
Escape characters that have special meaning when you want them literally. A period matches any character in many regex contexts, while \. matches a literal period. The same principle applies to characters such as +, *, ?, ^, $, parentheses, brackets, braces, and the vertical bar.
Lookarounds check nearby text without including it in the match. For example, this pattern finds a number immediately followed by a percent sign:
const percentages = '10% and 25%'.match(/\d+(?=%)/g);
// ['10', '25']A negative lookahead, such as (?!...), can exclude a following pattern. Lookarounds are powerful, but a clear two-step JavaScript check may be easier to maintain when the condition becomes complex.
Unicode text and user input
Patterns written with ranges such as [A-Za-z] describe a narrow Latin alphabet. They are not a general solution for names, places, or user-generated text in every writing system. For Unicode-aware matching, investigate property escapes such as \p{L} with the u flag where your supported environments allow them:
const letters = /\p{L}+/gu;
const words = 'Cafe 東京'.match(letters);Test Unicode patterns with representative data, including accented characters, combining marks, emoji, and scripts your application actually accepts. “Character” boundaries can be more complicated than one JavaScript code unit.
Cadence and checkpoints
Treat regexes as small pieces of application logic rather than write-once snippets. Review them when a form field, API payload, log format, or import source changes. A monthly or quarterly review is useful for shared validation utilities and high-traffic input paths; smaller private patterns can be checked during normal code review.
At each checkpoint, inspect four areas:
- Fixtures: Keep valid, invalid, empty, boundary, and unusual examples. Include values with whitespace, punctuation, newlines, and non-ASCII characters when relevant.
- Intent: Confirm that the pattern still reflects the product rule. A regex can remain technically correct while the requirement has changed.
- Runtime target: If you add named groups, Unicode properties, lookbehind, or another newer feature, verify that your browser, Node.js, test runner, and build targets support it.
- Performance: Exercise long and adversarial inputs. Avoid deeply nested quantifiers and ambiguous alternatives that can cause excessive backtracking.
A small test table is often enough:
const cases = [
['12345', true],
['123', false],
['', false],
['12345 ', false]
];
for (const [value, expected] of cases) {
console.assert(/^\d{5}$/.test(value) === expected, value);
}For patterns used on live user input, test both the browser-side behavior and the server-side rule where applicable. Client-side validation improves feedback, but it should not be treated as the only enforcement boundary for data quality or authorization.
How to interpret changes
When a regex starts failing, first classify the change. A new failing example may reveal an incorrect assumption, a changed input format, a missing flag, or a runtime compatibility issue.
If valid text no longer matches, compare the smallest failing input with the smallest passing input. This often exposes a boundary problem: an anchor is too strict, a character class excludes punctuation, or a quantifier requires one occurrence where zero should be allowed. If invalid text begins to pass, add a counterexample and check whether the pattern is searching for a substring instead of validating the whole value.
Separate syntax from semantics. A regex can confirm that a date resembles YYYY-MM-DD without proving that the month and day form a real calendar date. Likewise, an email-like pattern can check a limited application format without being a complete definition of every valid email address. Use ordinary JavaScript or a domain-specific parser for rules that require calculation or structured interpretation.
Be careful with the stateful behavior of global and sticky regexes when reusing a RegExp object with methods such as test(). The lastIndex property can affect subsequent calls. A literal created for each check, or deliberate management of the regex state, makes the behavior easier to predict.
For security-sensitive or externally controlled input, prefer bounded patterns and explicit limits. Do not use a complex regex as a substitute for input size limits, parsing, authorization, or safe output handling. If a pattern is difficult to explain, split the operation into multiple readable checks.
When to revisit
Revisit this guide and your project’s regex tests when you add a new input language, change a form requirement, alter an API or log format, support additional locales, or update the JavaScript environments your application targets. Also review patterns after a bug report that contains a previously untested value; the new example should become a regression test.
For a practical maintenance routine, keep a short record beside each nontrivial pattern: what it matches, what it intentionally rejects, which flags it uses, and where the tests live. During the next monthly or quarterly checkpoint, run those fixtures against the current runtime targets, inspect any new Unicode or formatting requirements, and simplify patterns that have accumulated exceptions.
If you need to debug a failing expression, use a trusted regex tester or a small local test file, then confirm the result in the same JavaScript runtime used by the application. A browser-based regex tester can help visualize matches, but the final check should include your actual escaping, flags, input normalization, and production test suite.
Start with one recipe, give the pattern a descriptive constant name, add at least one passing and one failing test, and document its limits. That workflow is more reliable than copying a broad expression and discovering its assumptions later.