The ECMAScript flavour and its limits
Patterns here run through the browser’s own RegExp engine, so what you see is exactly what JavaScript does — the flavour defined in ECMA-262 and used by Node, Deno, TypeScript and every browser. That includes lookahead and lookbehind ((?=…), (?<=…)), named groups ((?<year>…)), backreferences and Unicode property escapes such as \p{L} or \p{Script=Hangul} when the u flag is on.
What ECMAScript does not have: atomic groups (?>…), possessive quantifiers (a*+), recursion (?R), conditionals, \A and \z anchors, and the free-spacing x flag. A pattern copied from PHP’s PCRE, Python’s re or .NET may therefore need small edits — most often replacing (?P<name>…) with (?<name>…).
Supporting those other flavours would mean running your pattern and your test data — usually a slice of production logs — on someone’s server. Naming the limit is the honest trade: everything on this page stays in the tab.
What each flag changes
- g (global): find every match instead of stopping at the first. Without it the match list has at most one entry, and replace changes only the first occurrence.
- i (ignore case): ASCII and Unicode case folding, so /straße/i matches STRASSE only under u.
- m (multiline): ^ and $ match at every line break, not just at the start and end of the whole string.
- s (dotAll): . also matches a newline. Without it, . stops at line ends — the usual reason a pattern fails on multi-line text.
- u (unicode): treats the pattern and input as code points, which is what makes \p{…} work and stops emoji and rare CJK characters from being split into surrogate halves.
- y (sticky): matches only at lastIndex — used by tokenizers, rarely useful for searching.
- d (indices): records the start and end offset of every group. We switch it on regardless, because it is what lets the highlighting colour each capture group inside a match.
Reading the match table
Every match shows its start and end offset, the matched text, each numbered group and each named group. Numbering follows the opening parentheses from left to right, so nesting counts: in /((a)(b))/ group 1 is "ab", group 2 is "a" and group 3 is "b". Use (?:…) when you want grouping without a capture.
A group that exists in the pattern but did not take part in the match is shown as undefined — different from an empty string, and a distinction that matters when you index into the result in code. Alternation is the usual source: /(a)|(b)/ always leaves one of the two undefined.
Matching stops at 10,000 results; above that the table says how many exist in total. That is a display limit, not a correctness one — the count you see is real.
Replacement syntax
In the Replace tab the replacement string follows String.prototype.replace: $1 … $99 insert numbered groups, $<name> inserts a named one, $& is the whole match, $` and $' are the text before and after it, and $$ is a literal dollar sign. Everything else is copied through untouched.
Two habits save time. Reorder with groups instead of writing two patterns — /(\d{4})-(\d{2})-(\d{2})/ with $3/$2/$1 turns 2026-09-06 into 06/09/2026. And remember that without the g flag only the first match is replaced, which is the most common "why did only one change" question.
Catastrophic backtracking (ReDoS)
A backtracking engine tries alternatives until one fits. Nest one quantifier inside another — (a+)+$, (\s*,)*, (\w|\d)*@ — and the number of ways to split the input grows exponentially, so a 30-character string can take longer than the age of the universe to reject. The same pattern on a server is a denial-of-service bug, catalogued as CWE-1333.
Every match here runs inside a Web Worker with a two-second watchdog: if the pattern has not finished, the worker is terminated and you get a timeout message instead of a frozen tab. The pattern box also warns when it spots the nested-quantifier shape, before you run it.
The fix is almost always to make the inner part unambiguous: replace (a+)+ with a+, anchor the pattern, use a negated character class ([^"]*) instead of a lazy .*?, and split the work into two simpler passes rather than one clever expression.
Gotchas that look like engine bugs
\w means [A-Za-z0-9_] and \d means [0-9] — ASCII only. \b is defined in terms of \w, so it does not sit between Korean or accented words the way you expect: /\b한국\b/ behaves oddly because 한 is not a word character. Use \p{L} with the u flag, or lookarounds on whitespace, for non-ASCII text.
A dot inside a character class is literal: [.] needs no escape, while . outside one matches almost anything. And a global regex object carries lastIndex between calls, so reusing one across .test() calls gives alternating true and false — the reason this tool resets it before every run.
Finally, some things regex cannot do well. Nested structures — HTML, brackets, JSON — need a parser, not a pattern; and no regular expression fully validates an email address as RFC 5322 defines it. The built-in starter patterns are pragmatic filters, not specifications.
Sharing a pattern without leaking data
Share link puts the pattern, the flags and the test string after the # of the URL. Browsers never include the fragment in an HTTP request, so the shared data cannot appear in our access log, a CDN’s, a proxy’s or a referrer header — unlike the query-string sharing other testers use.
The link still travels wherever you send it, so treat a shared URL like the log sample it contains. Your working pattern and text are also kept in this browser’s localStorage so a reload does not lose them; Clear removes both.