Regex Tester
Test a regular expression against your own text and see every match highlighted as you type, with its position, capture groups and a live replace preview. It runs on your browser's built-in JavaScript engine, so the results are exactly what the same pattern does in JavaScript and Node.js code.
0matches
Highlighted matches
Match details
$& whole match · $1, $2… numbered group · $<name> named group · $$ a literal dollar sign
Result after replace
How to use the Regex Tester
- Type the pattern between the slashes. Pasting a whole literal such as
/\d+/giinto the empty field splits it into pattern and flags. - Tick the flags you need. Keep
gon to find every match. - Enter your text under Test string. Matches are highlighted in two alternating colors; a thin bar marks an empty (zero-length) match.
- Read Match details for the index, length and value of each match and its capture groups.
- Optionally type a replacement string to preview the replaced text, then copy it.
- Or start from one of the Common patterns; if you haven't edited the test string, it switches to a matching sample.
How the tester works
The page compiles your input with new RegExp(pattern, flags), so syntax rules, error messages and results come straight from your browser's engine (V8 in Chrome and Edge, SpiderMonkey in Firefox, JavaScriptCore in Safari). Everything runs locally in the browser; nothing you type is sent to a server.
Matches are collected with an exec() loop, the same thing matchAll() does internally:
re.lastIndex = 0;
while ((m = re.exec(text)) !== null) {
record(m); // index, value, groups
if (m[0] === '') re.lastIndex++; // step past an empty match
}
The last line matters. Patterns such as ^ with m, \b or x* can match an empty string, which leaves lastIndex unchanged; without the manual step the loop would find the same empty match forever. (With u the step is a whole code point, so an emoji is never split.) So /^/gm on three lines gives exactly three matches.
To stay responsive, the tester stops at 1,000 matches and stops highlighting after about 100 ms of matching, and tells you when it does. These limits are checked between matches, so they can't interrupt one exec() call stuck backtracking; if a run takes over a second, live updates pause until you press Test.
The replace preview follows the rules of String.prototype.replace():
| Token | Inserts |
|---|---|
$& | the whole match |
$1 … $99 | a numbered group (empty if that group did not take part) |
$<name> | a named group |
$` $' | the text before / after the match |
$$ | a literal dollar sign |
Without g only the first match is replaced, as in code; replaceAll() even throws a TypeError for a regex without g.
Examples
1. Runs of digits
Pattern /\d+/g, test string a1b22c333. The tester reports 3 matches: 1 at index 1, 22 at index 3 and 333 at index 6. Because + is greedy, each match takes the whole run of digits instead of stopping after one. Untick g and only the first match, 1, remains.
2. Named groups and reformatting
Pattern /(?<y>\d{4})-(?<m>\d{2})/g on Released 2026-09, patched 2026-11 finds 2026-09 at index 9 and 2026-11 at index 26. For the first match, Match details show group 1 (y) = 2026 and group 2 (m) = 09. With the replacement $<m>/$<y> the preview reads Released 09/2026, patched 11/2026. Named groups still have numbers, so $2/$1 gives the same result.
3. Rewriting part of a match
Pattern /(\w+)@/ with the replacement $1 at (note the trailing space) turns Write to jane@example.com into Write to jane at example.com. The whole match jane@ is replaced, and $1 puts the captured name back.
Regex cheat sheet (JavaScript)
| Syntax | Meaning |
|---|---|
| Character classes | |
. | any character except a line break (any at all with s) |
\d \D | a digit 0–9 / anything else |
\w \W | a word character [A-Za-z0-9_] / anything else |
\s \S | whitespace, including tabs, line breaks and Unicode spaces / anything else |
[abc] [^abc] | one of a, b, c / any character except those |
[a-z0-9] | a character from a range |
\p{L} | any Unicode letter (needs u) |
| Anchors | |
^ $ | start / end of the input, or of each line with m |
\b \B | a word boundary / not a word boundary |
| Quantifiers | |
* + ? | 0 or more / 1 or more / 0 or 1 |
{3} {3,} {3,5} | exactly 3 / 3 or more / 3 to 5 |
*? +? {3,5}? | lazy versions: as few repetitions as possible |
| Groups and references | |
(abc) | capturing group, numbered from 1 by its opening parenthesis |
(?<name>abc) | named capturing group |
(?:abc) | group without capturing |
\1 \k<name> | backreference: the same text a group matched |
a|b | a or b |
| Lookahead and lookbehind (zero-width) | |
(?=abc) (?!abc) | followed by / not followed by abc |
(?<=abc) (?<!abc) | preceded by / not preceded by abc |
| Flags | |
g i | global (all matches) / ignore case |
m | multiline: ^ and $ also match at line breaks |
s | dotAll: . also matches line breaks |
u | Unicode: whole code points, \p{…}, stricter syntax |
y | sticky: match only exactly at lastIndex |
d | indices: record the start and end of every group |
Greedy vs. lazy quantifiers
Quantifiers are greedy by default: they take as much text as they can and give characters back only when the rest of the pattern fails. On the text <b>bold</b> and <i>italic</i>:
/<.+>/gfinds 1 match, the entire line, because.+runs to the end and backs up only as far as the last>./<.+?>/gfinds 4 matches:<b>,</b>,<i>and</i>. The lazy+?stops at the first>that lets the match succeed./<[^>]+>/gfinds the same 4 tags without any backtracking, since[^>]can never run past a>. A negated class is usually the clearest and fastest choice.
JavaScript regex vs. PCRE and Python
Most syntax is shared, but a pattern copied from PHP (PCRE) or Python can behave differently here:
- Named groups: JavaScript writes
(?<name>…); Python'sreneeds(?P<name>…); PCRE accepts both. - Replacements:
$1and$<name>here,\1and\g<name>in Python'sre.sub(). - End of input: no
\A,\zor\Z; use^and$withoutm. JavaScript's$matches only at the very end; PCRE and Python also accept it before a final line break. - No atomic groups or possessive quantifiers (
(?>…),a++). PCRE has both, Python since 3.11. Recursion and conditionals are PCRE-only. - ASCII shorthands:
\d,\wand\bknow only ASCII, even withu; Python 3 makes them Unicode by default. Use\p{Nd}or\p{L}withu. - Lookbehind may have any length in JavaScript; Python requires a fixed width.
- Inline flags: flags apply to the whole pattern. Modifier groups such as
(?i:…)arrived only in ES2025, so older browsers reject them.
Catastrophic backtracking
JavaScript uses a backtracking engine: when part of a pattern fails, it goes back and tries another way to match what came before. Nested quantifiers can make that explode. Take /(a+)+$/ on aaaaaaaaaaaaaaaaaaaaaaaa! (24 a's). The $ can never match before the !, yet the engine first tries every way of splitting the a's between the inner and outer + (24, 23 + 1, 22 + 2, 22 + 1 + 1…): 2n−1 splits for n letters.
Each extra a roughly doubles the time. In our test in Node.js (V8, the engine in Chrome and Edge) on a laptop, 30 a's took about 14 seconds, and a browser tab stays frozen meanwhile. On a server, such a pattern plus untrusted input is a denial-of-service hole known as ReDoS. The fixes:
- Remove the nesting:
a+$matches the same text and fails instantly. - Make repetition unambiguous:
\w+(?:\s\w+)*instead of(\w+\s?)*leaves only one way to split the words. - Emulate an atomic group with
(?=(a+))\1: the lookahead captures once, and the backreference consumes exactly that text. - Limit the input length before matching text you don't control.
The tester warns when it spots a repeated group containing its own + or *, such as (a+)+ or (.*)*. It's a heuristic: it can't catch every slow pattern, and a flagged one may still be fine on your data.
Frequently asked questions
Which regex flavor does this tester use?
JavaScript (ECMAScript), run by your browser's own engine, so a pattern behaves the same in browser JavaScript, Node.js, Deno and Bun with the same flags. For other languages, see the differences above.
Why does my regex find only one match?
The g flag is off, so exec(), match() and replace() stop at the first match. In code, text.match(/x/g) returns all matched strings without groups; text.matchAll(/x/g) gives you both.
How do I match a literal dot, parenthesis or slash?
Put a backslash in front: \., \(, \?, \\. Inside square brackets most characters lose their meaning, so [.] matches a dot too. A slash needs escaping only in a /literal/ in source code; Copy as /literal/ does that for you. In a string passed to new RegExp(), double every backslash: "\\d+".
What is the difference between the m and s flags?
m makes ^ and $ match at the start and end of every line; s lets . match line breaks. They are independent: m for line-by-line work, s to grab a block spanning several lines.
Is there a regex that validates every email address?
Not a practical one. RFC 5322 allows quoted local parts, comments and IP-address domains; a pattern covering all of it is enormous and still can't tell whether a mailbox exists. Even the HTML standard calls its type="email" rule a willful violation of RFC 5322. Use a simple pattern like the one under Common patterns, then send a confirmation email.
Related tools
- JSON Formatter & Validator – pretty-print and check JSON before you parse it.
- Word Counter – count words, characters and sentences in any text.
- Color Contrast Checker – test the hex colors you just matched against WCAG contrast ratios.
- Unit Converter – convert length, weight and temperature units.