What a regular expression is
A regular expression is a small pattern language for describing shapes of text. Instead of asking whether a string equals something, you describe what it must look like — four digits, then a hyphen, then two more digits — and the engine reports every place that shape occurs.
\d{4}-\d{2} → finds 2026-08 in 2026-08-11
Most characters stand for themselves; the power is in the handful that do not. A dot means any character, a plus means one or more of whatever came before, square brackets enclose a set to choose from, and parentheses group a run so a quantifier or an alternation can act on the whole of it. Reading one is a matter of taking it token by token, which is exactly what the Explanation panel does: every line names one construct and quotes the slice of the pattern it describes.
The building blocks
Ten families cover almost everything you will write. The tokens below are ECMAScript syntax; where another flavour differs, the engine section says so.
| Tokens | What it does | For example |
|---|---|---|
| ^ $ \b \B | Assert a position without consuming a character. | ^cat matches cat only at the start. |
| [abc] [^abc] [a-z] . | Match one character out of a set, or out of everything but a set. | [aeiou] matches any one vowel. |
| \d \w \s \D \W \S | Named sets: digit, word character, whitespace, and their negations. | \d\d matches two digits in a row. |
| * + ? {2} {2,} {2,6} | Repeat the previous token, greedily by default. | colou?r matches both spellings. |
| *? +? ?? {2,6}? | The same repetitions, taking as little as they can. | <.+?> stops at the first closing bracket. |
| (…) (?:…) (?<name>…) | Group a run, and capture it so it can be reused or reported. | (?<year>\d{4}) captures four digits as year. |
| a|b | Try the left side, then the right. | cat|dog matches either word. |
| (?=…) (?!…) (?<=…) (?<!…) | Assert that text does or does not appear either side, without consuming it. | \d+(?= kg) matches a number only before kg. |
| \1 \k<name> | Match the same text a capture group matched earlier. | (\w)\1 matches a doubled letter. |
| \. \n \t \x41 \u{1F600} \p{L} | Write a literal metacharacter, a control character, or a code point. | \. matches a full stop, not any character. |
When you want a metacharacter to be an ordinary character, put a backslash in front of it. That is why an email pattern reads \. in the middle: an unescaped dot would match any character at all, and the pattern would happily accept name@examplezcom.
Every flag
Eight switches, opened from the letters at the right-hand end of the pattern field. Six are real ECMAScript flags the engine reads directly. Two are borrowed from PCRE, and this tool applies them by rewriting the pattern before it compiles.
| Flag | What it changes | Reach for it when |
|---|---|---|
| g global | Keeps searching after the first match instead of stopping there. | You want every occurrence — which is nearly always, when testing. |
| m multiline | Re-points ^ and $ at the start and end of each line rather than the whole string. | The input is a list, one entry per line. |
| i insensitive | Matches letters regardless of case. | The casing carries no meaning — a keyword, a domain name, a header. |
| s single line | Lets the dot match line terminators, which it otherwise refuses. | A pattern has to span lines, as when grabbing the body of an HTML element. |
| x extended | Ignores unescaped whitespace and treats # as the start of a comment. | A long pattern needs breathing room and notes; escape a space as \ to keep it. |
| u unicode | Treats the pattern as code points and enables \p{…} property escapes and \u{…}. | The text is not plain ASCII — emoji, accents, or non-Latin scripts. |
| U ungreedy | Inverts every quantifier: greedy ones become lazy, and an explicit ? makes them greedy again. | You are porting a PCRE pattern that relied on it; writing +? is clearer in new work. |
| y sticky | Anchors each attempt at the position the previous match ended, so a gap stops the search. | Tokenising, where a run of matches must be contiguous. |
Extended and ungreedy do not exist in JavaScript. Rather than pretend otherwise, this tool rewrites the pattern: under extended it strips the whitespace and comments the parser ruled inert, and under ungreedy it flips each quantifier in place. The pattern you typed is what gets highlighted and explained; the rewritten one is what the engine runs, and it is shown in full when a compile fails.
Global and multiline are on by default, which is what makes a list of test lines behave the way most people expect. Everything else is off.
Match, substitution, and list
The three modes share one pattern and one test string; they differ in what they do with the matches.
Match highlights each match in place and lists it in the Match information panel with its position and every capture group. A group that took no part in a match is reported as such, which is not the same as a group that matched the empty string — a distinction that decides what $1 inserts.
Substitution rebuilds the whole test string with each match replaced. It follows String.prototype.replace exactly, so what you see in the result box is what that call would return in your own code.
List gives one line per match, each expanded through the same replacement tokens. Leave the box empty and you get the matches themselves — useful for pulling a column out of a log.
| Token | Inserts |
|---|---|
| $& | The whole match. |
| $1 | Capture group 1; use $2, $3, and so on for the rest. |
| $<name> | The group declared as (?<name>…). |
| $` | Everything in the test string before the match. |
| $' | Everything in the test string after the match. |
| $$ | A literal dollar sign. |
One difference worth knowing if you arrived from regex101: there the whole match is written $0. JavaScript reads $0 literally, and this tool shows what JavaScript does rather than a friendlier fiction, so the whole match is $&. A group number beyond what the pattern declares is likewise left as written — $12 against a pattern with one group inserts group 1 followed by the character 2.
Why there is a delimiter picker
The delimiter never reaches the engine. It decides how the pattern is written down when it leaves this page — as /…/ in JavaScript, or ~…~ in a PHP codebase that prefers it — and therefore which character has to be escaped inside the literal.
That escaping is the practical part. A pattern matching a path is full of slashes, and inside a slash-delimited literal every one of them needs a backslash. Switch the delimiter to a tilde and they can stay as they are, which is why PCRE offers the choice at all.
- /Slash
- ~Tilde
- @At sign
- ;Semicolon
- %Percent
- `Backtick
- #Hash
Pasting works the other way round. Drop a complete literal into the pattern field and the delimiter, the body, and the flags are all read off it — so a pattern copied out of source code arrives intact, with its escaping undone. A bare pattern is left exactly as pasted; only a well-formed literal is unwrapped.
Sharing goes out the same way. Share copies a link carrying the pattern, the flags, the delimiter, the mode, the replacement, and the test string, so a colleague opens exactly what you are looking at. Only what differs from the defaults is written, which keeps an ordinary link short; a test string too large to fit is left out and the tool says so rather than sending a link that quietly lost half its meaning.
Which engine runs your pattern
Matching runs on the JavaScript engine already in your browser. There is no other option: a browser cannot execute PCRE, Python's re, or Go's RE2, and a tool claiming to would be reporting one engine's answers under another's name.
Nearly everything is shared. Anchors, classes, shorthands, quantifiers, groups, alternation, backreferences, and all four lookarounds behave the same in JavaScript as in PCRE or Python. What JavaScript does not have is atomic groups, possessive quantifiers, recursion, subroutine calls, and inline flag switches of the (?i) form. Rather than let those fail with a message about an invalid group, this tool names the construct and points at its position before compiling.
Two smaller differences are worth remembering when a pattern is going somewhere else. Go's RE2 and Rust's regex crate have no lookaround or backreferences at all, by design — they trade those for a guarantee against the backtracking described below. And \A, \z, and \Z are PCRE anchors; in JavaScript they are ordinary escaped letters, so \Afoo quietly matches the text Afoo. The Explanation panel says so in as many words.
Catastrophic backtracking, and the limits here
A backtracking engine — JavaScript's, PCRE's, Python's — tries alternatives until one fits. Usually that is quick. But nest one unbounded quantifier inside another, as in (a+)+b, and the number of ways to divide the input between them grows exponentially. Against a string of thirty a characters with no b, the engine will still be working long after you have closed the tab.
This tool warns about that shape as you type. The warning is a heuristic, not a proof: it fires on a quantified group that contains another quantifier, which is the pattern behind most real incidents, and it will occasionally flag something that is perfectly safe.
The real protection is architectural. Matching runs in a web worker rather than on the page, because a running regular expression cannot be interrupted from inside — no timeout, no cancellation, nothing. What the page can do is stop waiting: after two seconds it terminates the worker outright and starts a fresh one on your next keystroke. The tab stays responsive whatever the pattern does.
Three ceilings sit underneath that: a pattern of 1,000 characters, a test string of 50,000, and 1,000 matches. When the match cap is reached the panel says so rather than quietly showing a short list. There is also a time budget inside the match loop itself, which stops a global pattern grinding through a long input — though only between matches, never inside one, which is precisely why the worker exists.
Frequently asked questions
$0 as two ordinary characters. regex101 defines it as the whole match, but the substitution preview here is meant to show what String.prototype.replace would actually return, so it follows JavaScript's tokens exactly. Write $& for the whole match.