Regex tester
Match, capture and replace against your own text using your browser's own regular expression engine — the same one your JavaScript code runs on. Nothing you type here is sent anywhere, and this tool makes no network requests after it loads.
Without g or y, only the first match is found — this mirrors what regex.exec() itself returns, not a simplification made for this page.
Highlighted
Decorative only — the match list below is the full, accessible breakdown of every match.
Nothing to match yet.
Without the g flag this replaces only the first match — the same rule String.replace() applies everywhere, not just here.
About this tool
Type or paste a pattern, tick the flags you want, and the text below is matched against it as you type. Every match is highlighted, its numbered and named capture groups are broken out underneath, and a separate replace field runs the same pattern through String.replace() so you can see the substitution before you paste it into code.
There is no separate "run" step and no separate regex dialect to pick. What you see here is exactly what your own JavaScript would do with new RegExp(pattern, flags) — because that is literally what this page calls.
How it works
Matching runs on the browser's built-in RegExp engine only — there is no hand-written regex engine here and no library, bundled or fetched. Compiling the pattern, finding matches and running the replace are three direct calls: new RegExp(pattern, flags), a loop of .exec(), and text.replace(regex, replacement).
With the g or y flag, matches are found by calling .exec() repeatedly against the same regex object, which is what makes the search continue from where the last match ended (lastIndex) rather than restart from the beginning. A pattern that can match a zero-length string — a* against text with no as, for instance — has its position nudged forward by one character after an empty match, or the loop would never advance and would run forever; every real regex engine that supports global matching has to do the same thing internally.
Capture group numbers are read directly from the match result, and the label next to each one (a group's name, if it has one) is worked out by scanning the pattern text for (?<name> in order, skipping escaped characters, character classes, and non-capturing or lookaround groups that do not consume a number. This is the same counting a regex engine itself does — it is not guessed from the matched text.
Common questions
Why does my pattern only find one match?
Because the g (global) flag is not ticked. Without it, regex.exec() stops at the first match and this page shows exactly that, rather than quietly behaving as if global were always on.
What is the difference between a numbered and a named group?
Every parenthesised group in the pattern gets a number automatically, counted left to right by its opening parenthesis — (?:...) non-capturing groups and lookaround don't count. A named group, written (?<name>...), gets a number and a name; both work in the replacement string, as $1 or as $<name>.
Why did my replacement come out with a literal $1 in it?
Either the pattern has fewer capture groups than the number used, or the replacement string was built by concatenating a variable into it (which turns the $1 into inert text) rather than being passed as a genuine second argument to .replace(). This page always passes it as the real argument, so if $1 shows up literally here, the group number in the pattern doesn't exist.
Does this tool run PCRE, Python, or grep patterns?
No — only JavaScript's own RegExp. See the flavour reference further down for where those differ, so a pattern written for one of them can be adjusted before it's tested here.
When a pattern fails to compile
An invalid pattern throws a real SyntaxError — this page catches it and shows the browser's own message above the pattern field, rather than replacing it with a generic "invalid pattern" notice. Chromium-based browsers (and Node.js, which uses the same V8 engine) report messages like these for common mistakes:
Invalid regular expression: /(unclosed/: Unterminated groupAn opening(with no matching).Invalid regular expression: /a)/: Unmatched ')'A closing)with no matching(.Invalid regular expression: /*abc/: Nothing to repeatA quantifier (*,+,?,{n}) with nothing before it to repeat — also the message for a doubled-up possessive-looking++, which JavaScript has no notion of.Invalid regular expression: /[abc/: Unterminated character classAn opening[with no matching].Invalid regular expression: /(?<n>a)(?<n>b)/: Duplicate capture group nameThe same named group used twice in one pattern.
Firefox and Safari word these differently but throw for the same reasons — whatever your own browser reports is what appears above the pattern field, verbatim, not a message rewritten by this page.
The same job at the command line
grep -E 'pattern' file.txtPOSIX extended regular expressions (ERE) — closer to JavaScript than plaingrep's basic (BRE) syntax, but see the flavour reference below for where it still diverges.grep -P 'pattern' file.txtPCRE syntax, on GNU grep with-Psupport (not available on macOS's BSD grep without GNU grep installed separately).python3 -c "import re; print(re.findall(r'pattern', open('file.txt').read()))"Python'sremodule — see the flavour reference for its syntax differences from JavaScript.perl -pe 's/pattern/replacement/g' file.txtPerl, the syntax PCRE was built to match. Drop-pfor a bare match instead of a substitution.node -e "console.log([...require('fs').readFileSync('file.txt','utf8').matchAll(/pattern/g)])"The exact engine this page uses, from the command line.
Flavour reference — JavaScript against PCRE, Python and POSIX
This page only executes JavaScript's own RegExp — the table below is reference material, not a second engine running underneath. A pattern copied from a PCRE-based tool (Perl, PHP, most "regex101"-style testers), from Python's re module, or from grep, may need adjusting before it means the same thing here. Every row was checked against each engine's own documentation or observed behaviour before being written down.
| Feature | JavaScript RegExp | PCRE (Perl, PHP) | Python re | POSIX ERE / BRE (grep) |
|---|---|---|---|---|
| Named group | (?<name>…) only |
(?<name>…) or (?P<name>…) |
(?P<name>…) only |
Not supported |
| Named backreference | \k<name> |
\k<name> or (?P=name) |
(?P=name) |
Not supported |
| Named group in replacement | $<name> |
Engine-dependent, often ${name} |
\g<name> |
n/a — sed has no named groups |
Lookbehind (?<=…) (?<!…) |
Yes, since 2017–2023 across engines (Safari was last, version 16.4) | Yes, long-standing | Yes, but the matched text must be a fixed width — no *, +, or alternatives of different lengths |
Not supported |
Lookahead (?=…) (?!…) |
Yes | Yes | Yes, any width | Not supported |
Atomic group (?>…) |
Not supported, no equivalent | Yes | Python 3.11+ only (added October 2022) | Not supported |
Possessive quantifiers a++ a*+ |
Not supported — a++ is a syntax error ("nothing to repeat") |
Yes | Python 3.11+ only | Not supported |
Lazy quantifiers *? +? |
Yes | Yes | Yes | Not supported — POSIX matching is always greedy, with no lazy form at all |
| Inline flags, e.g. case-insensitive | Not supported anywhere in the pattern — flags are only ever passed alongside it | (?i) for the rest of the pattern, (?i:…) scoped to a group |
(?i) only at the very start of the pattern (Python 3.11+ errors elsewhere); (?i:…) scoped |
Not supported — grep -i is a command-line flag, not part of the pattern |
\d \w \s scope |
Always ASCII only, with or without the u flag — Unicode categories need \p{…} |
ASCII by default; Unicode with the PCRE_UCP option |
Unicode-aware by default for text patterns; re.ASCII restricts to ASCII |
No such shorthand in the standard — GNU grep accepts \w/\d as a non-portable extension |
| POSIX class, e.g. digits | Not supported — write [0-9] or \d by hand |
[[:digit:]] inside a bracket expression |
Not supported — [:digit:] is read as the literal characters :, d, i, g, t |
[[:digit:]], [[:alpha:]], [[:alnum:]] etc. — the canonical form |
Unicode property escape \p{L} |
Yes, but only with the u (or newer v) flag set |
Yes, with PCRE_UCP |
Not supported in re — needs the third-party regex package |
Not supported |
Conditional pattern (?(1)yes|no) |
Not supported | Yes | Yes | Not supported |
Recursion / subroutines (?R) (?1) |
Not supported | Yes | Not supported in re — needs the third-party regex package |
Not supported |
| Sticky / continue-from-end matching | y flag |
\G anchor |
No dedicated flag — Pattern.match(string, pos) achieves the same thing via the API |
n/a |
| Dot matches line breaks | s flag (dotAll) |
s modifier / PCRE_DOTALL |
re.DOTALL or inline (?s) |
n/a — utilities like grep process one line at a time, so this rarely arises |
Parenthesis ( literal vs. group |
( groups, \( is literal |
( groups, \( is literal |
( groups, \( is literal |
ERE: same as the others. BRE: reversed — \( groups, bare ( is literal |
Backreference \1 |
Yes | Yes | Yes | BRE: yes, standard. ERE: not in the POSIX standard, though GNU's implementation accepts it anyway |
"POSIX ERE / BRE" above describes the standard and GNU grep's behaviour specifically — other grep, sed and awk implementations (BSD/macOS in particular) vary further and are worth checking with man before relying on any of this at the command line.
More tools
See the whole toolbox — thirty-seven free tools planned, all running in your browser.