tooloora

Regex Tester — matches, groups and replace, without freezing the tab

Type a pattern, see every match highlighted with its numbered and named groups, and preview a replacement. Patterns run in a Web Worker with a hard time limit, so a catastrophic one gets cancelled instead of locking up your browser.

Runs locally — nothing is uploaded

g
Pattern library

One click loads the pattern, its flags and a sample text. These patterns are starting points to try out, not validation rules: a match only tells you that the shape fits.

$1 to $99 insert numbered groups, $<name> inserts a named group, $& the whole match, $$ a single dollar sign.

Parts of the pattern

As soon as a pattern is entered above, it is explained here piece by piece.

Why a separate compute thread?

A pattern such as (a+)+b needs billions of backtracking steps on 30 characters, and a running regular expression cannot be cancelled from the inside. That is why your input runs in a Web Worker: if it does not answer in time, it is terminated from the outside and the page stays usable.

Limits

Up to 100,000 characters of test text and 1,000 matches are evaluated. The search gives up after 1 second of computation.

Runs locally in your browser — pattern and test text are never sent anywhere.

Test regular expressions without risking the tab

Type a pattern above, paste your test text, and see every match highlighted right away: numbered and named groups per match, a replacement preview for $1 and $<name>, and an explanation of each building block. Everything runs in your browser, on a separate thread with a hard time limit. No upload, no sign-up.

Why would a regex tester freeze at all?

Because a running regular expression cannot be cancelled from the inside in JavaScript. The pattern (a+)+b looks harmless, yet on 30 characters it forces the engine through billions of backtracking steps, because it tries every possible split of the a characters before giving up. While it computes, the whole thread is stuck: no clicks, no scrolling, no error message.

The name for this is ReDoS (Regular Expression Denial of Service), and it is not a laboratory curiosity. On 2 July 2019 a single regular expression with catastrophic backtracking, shipped inside a new firewall rule, brought Cloudflare's global network down for 27 minutes; the company documented the incident in detail itself. If such a pattern can take out a data centre, it will certainly take out a browser tab.

That is why this tool is built the way it is: your pattern runs in a Web Worker, a thread of its own, while the main thread sets an alarm. If the worker does not answer in time it is terminated from the outside and a fresh one is started for the next request. Doing that from the inside is impossible, and that is precisely the difference from a tester that runs foreign patterns on the main thread.

How do you read capture groups correctly?

Every round bracket in the pattern remembers what it matched. (\d{4}) is group 1, the next bracket group 2, and so on. Named groups are clearer: (?<year>\d{4}) carries its purpose in the name and survives any later reshuffling of the pattern. When a bracket only needs to group but not to remember, write (?:…); that saves work and keeps the numbering clean.

The match list shows both side by side. The important distinction is between did not participate and empty: in (a)|(b), a match on b leaves the first group unused, so it has no value at all. A group like (x*) can very well participate and still hold empty text. Confusing the two is how replacements quietly produce wrong output.

What do $1 and $<name> do when replacing?

The replacement field follows the same rules as String.prototype.replace:

  • $1 through $99 insert the content of a numbered group
  • $<name> inserts the content of a named group
  • $& inserts the whole match, $` the text before it, $' the text after it
  • $$ inserts a single dollar sign

So 24.12.2026 with the template $<year>-$<month>-$<day> becomes 2026-12-24. The preview works on the matches that were already found, so the pattern is never executed a second time. That is not a detail: a second run on the main thread would be exactly the place where the page freezes after all.

Why is there no perfect email regex?

RFC 5322 allows quoted parts, bracketed comments and nested constructs. A fully compliant expression therefore runs to thousands of characters, is nearly unreadable, and still says nothing about whether a mailbox exists behind the address. The HTML standard works around this by defining its own, deliberately simpler pattern for input type="email" and openly labelling that simplification a willful violation of RFC 5322.

The pattern in the library follows the same line: it catches typing mistakes, and promises nothing more. If you really need to verify an address, send a confirmation message; everything else is cosmetics. The same holds for IBAN: the length differs per country (ISO 13616 allows 15 to 34 characters, Germany uses 22), and the actual check digits come from a modulo-97 calculation that no regular expression can perform. A pattern checks the shape, never the substance.

Where do patterns differ between browsers?

Named groups and lookbehind (?<=…) entered the language with ES2018, but did not arrive everywhere at once: Safari has supported lookbehind only since version 16.4 (March 2023). A pattern that runs cleanly in Chrome can throw a syntax error on an older iPhone. If your expression will end up in a customer's browser, it is worth checking here before it goes into the code.

Honest limits

Up to 100,000 characters of test text and 1,000 matches are evaluated; the worker gives up after one second of computation, the alarm in the main thread after 2.5 seconds. The match list shows the first 100 entries in full, while highlighting still covers all of them. For whole log files a command line tool such as ripgrep is the better instrument. And for the most common trap there is no limit, only attention: nested repetition such as (a+)+ or (\w+\s?)* is the blueprint for a pattern that may never come back.

Frequently asked questions

Why does this tester run patterns in a Web Worker?

Because a regular expression cannot be cancelled from the inside. A pattern like (a+)+b needs billions of backtracking steps on just 30 characters, and while the engine works, nothing else in that thread runs. In the main thread that means a frozen tab. Here every pattern runs in a separate worker thread with a hard time limit; when the limit is reached the thread is terminated and a fresh one starts, so the page stays usable. This class of problem has a name, ReDoS, and it is not theoretical: on 2 July 2019 a single regular expression with catastrophic backtracking took Cloudflare's global network down for 27 minutes.

Is my test text uploaded anywhere?

No. Pattern, flags and text stay in your browser; the worker is a local script served from this site, and there is no request that carries your data. That matters because test text is often production data: log excerpts, customer records, tokens. You can verify it in the network tab of your developer tools.

Is there a perfect regular expression for email addresses?

No, and anyone offering one is simplifying. RFC 5322 permits quoted local parts, comments and nesting, so a fully compliant expression runs to thousands of characters and still cannot tell you whether a mailbox exists. The HTML standard sidesteps this by defining its own, deliberately simpler pattern and calling it a willful violation of RFC 5322. The pattern in our library follows the same pragmatic line: it catches typing mistakes, it is not a validity check. The only real check is sending a confirmation message.

What do $1 and $<name> do in the replacement field?

They insert what a capture group matched. $1 refers to the first numbered group, $<name> to a group declared as (?<name>...). Additionally $& inserts the whole match and $$ a literal dollar sign. The preview applies these rules exactly as String.prototype.replace does, on the matches already found, so nothing is executed twice.

Which flags are supported and what do they change?

g finds every match instead of only the first, i ignores case, m makes ^ and $ match at line breaks, s lets the dot match a line break too, u switches on full Unicode mode, and y (sticky) requires each match to start exactly where the previous one ended. Note that lookbehind, written (?<=...), arrived late in Safari: it only works from Safari 16.4 onwards, so a pattern that runs fine in Chrome can fail on older iPhones.

Are there limits?

Yes, stated openly: up to 100,000 characters of test text and up to 1,000 matches are evaluated, the worker gives up after one second of computation, and the main thread terminates a silent worker after 2.5 seconds. The match list shows the first 100 entries in full; highlighting still covers all of them. For larger volumes a command line tool such as ripgrep is the better instrument.