Clever Quest Lab Tools

Regex Tester

2026-09-24

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.

Flags

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

    1. Type the pattern between the slashes. Pasting a whole literal such as /\d+/gi into the empty field splits it into pattern and flags.
    2. Tick the flags you need. Keep g on to find every match.
    3. Enter your text under Test string. Matches are highlighted in two alternating colors; a thin bar marks an empty (zero-length) match.
    4. Read Match details for the index, length and value of each match and its capture groups.
    5. Optionally type a replacement string to preview the replaced text, then copy it.
    6. 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():

    TokenInserts
    $&the whole match
    $1 … $99a 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)

    SyntaxMeaning
    Character classes
    .any character except a line break (any at all with s)
    \d \Da digit 0–9 / anything else
    \w \Wa word character [A-Za-z0-9_] / anything else
    \s \Swhitespace, 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 \Ba 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|ba 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 iglobal (all matches) / ignore case
    mmultiline: ^ and $ also match at line breaks
    sdotAll: . also matches line breaks
    uUnicode: whole code points, \p{…}, stricter syntax
    ysticky: match only exactly at lastIndex
    dindices: 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>:

    JavaScript regex vs. PCRE and Python

    Most syntax is shared, but a pattern copied from PHP (PCRE) or Python can behave differently here:

    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:

    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