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.

Pattern no pattern

Without g or y, only the first match is found — this mirrors what regex.exec() itself returns, not a simplification made for this page.

Test string empty

Highlighted

Decorative only — the match list below is the full, accessible breakdown of every match.

Matches empty

Nothing to match yet.

Replace empty

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:

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

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.

FeatureJavaScript RegExpPCRE (Perl, PHP)Python rePOSIX 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.