Regex Tester
Test regular expressions with live highlighting and a plain-English pattern breakdown.
Live — matches update as you type.
Contact ada@example.com or grace.hopper+lists@navy.mil for access. Invalid: not-an-email, foo@, @bar.com Escalation: oncall@forg.pro
Groups (first match)
| Group | Captured |
|---|---|
| $& | ada@example.com |
| $1 | ada |
| $2 | example.com |
| $<user> | ada |
| $<domain> | example.com |
Pattern, in plain English
(?<user>start of named capturing group "user"[\w.+-]+any one character from the set [\w.+-], repeated one or more times (greedy))end of group@the literal character "@"(?<domain>start of named capturing group "domain"[\w-]+any one character from the set [\w-], repeated one or more times (greedy)\.the literal character "."[\w.]+any one character from the set [\w.], repeated one or more times (greedy))end of group
Covers the common constructs (classes, quantifiers, anchors, groups, lookarounds). Anything else is labeled unrecognized rather than guessed at.
How it works
This tester runs your pattern through the real JavaScript RegExpengine — the one in your browser, the same family that runs in Node — so what matches here matches in your code. Type a pattern, toggle flags, and every match highlights live in the test string. Invalid patterns show the engine's own error message inline instead of silently matching nothing, which is the difference between debugging and guessing.
The plain-English breakdown walks your pattern token by token: character classes, anchors, quantifiers (with their greedy/lazy distinction), groups, alternation and escapes each get a one-line explanation in source order. It covers the constructs that make up the vast majority of real-world patterns; anything outside that set is labeled as unrecognized rather than misexplained. Reading your own pattern back in English catches a surprising number of bugs — usually a quantifier binding to the wrong token or an unescaped dot.
The groups table shows every capture from the first match, with named groups ((?<name>…)) labeled by name and unmatched alternatives shown as undefined. The replace preview applies your replacement string with full support for $1, $<name> and $& references, previewing the exact output of String.replace under the current flags — with g set it replaces every match, without it only the first.
Two safety rails are built in. The test string is capped at 50,000 characters, and matching runs inside a guard so that a pathological pattern degrades to an error message rather than a frozen tab — though genuinely catastrophic backtracking (nested quantifiers like (a+)+ over long inputs) can still be slow, which is itself a useful signal that the pattern needs restructuring before it meets production input. Everything runs locally; your test data never leaves the browser, so logs and real payloads are safe to paste here while you iterate on the pattern. When the pattern finally behaves, copy it straight into your code knowing the engine that validated it is the same one that will run it.
Frequently asked questions
What is the difference between greedy and lazy quantifiers?
Greedy quantifiers (*, +, {n,m}) match as much as possible and then backtrack; lazy ones (*?, +?) match as little as possible and expand only when forced. The classic symptom: `".*"` applied to `"a" and "b"` matches the whole span including ` and `, because .* grabs to the last quote. `".*?"` matches each quoted string separately. When a match is mysteriously too long, the fix is almost always a lazy quantifier or a negated character class like `"[^"]*"`.
Does JavaScript support lookarounds?
Yes, all four: lookahead (?=...), negative lookahead (?!...), lookbehind (?<=...) and negative lookbehind (?<!...). Lookbehind arrived in ES2018 and is supported in every current browser and Node version. Lookarounds are zero-width — they assert what surrounds a position without consuming characters — which makes them ideal for matching numbers followed by a unit, or words not preceded by a prefix, without including the context in the match.
How does the JavaScript regex flavor differ from PCRE or Python?
Mostly in the corners: JavaScript has no possessive quantifiers, no atomic groups (until the recent v-flag additions), no recursion, and uses (?<name>...) for named groups like Python but unlike older PCRE's (?P<name>...). \d \w \s are ASCII-only unless you enable the u flag with \p{...} unicode property escapes. Inline modifiers like (?i) are not supported — flags apply to the whole pattern. Test in the engine you ship.
What is catastrophic backtracking and how do I avoid it?
Patterns with nested or overlapping quantifiers — the canonical (a+)+ or (.*)* shapes — can force the engine to try exponentially many ways to partition the input before failing, freezing the thread for seconds or hours. Avoid quantifying a group that itself contains an open-ended quantifier over the same characters, prefer negated character classes over .*, and anchor patterns where possible. This tool caps the test string at 50k characters as a guard.
Why does my group table show 'undefined' for some groups?
A group that belongs to an unmatched alternative participates in the match attempt but captures nothing — JavaScript reports it as undefined rather than an empty string. For example, in /(a)|(b)/ matching the input 'b', group 1 is undefined and group 2 holds 'b'. An empty string capture, by contrast, means the group matched zero characters, which quantified groups like (x*) do legitimately.
Built by FORG — AI cost observability for agentic coding. Free tools, no signup, nothing leaves your browser.
Learn about FORG