Regex Tester & Replacer

Global tools

A regular expression is the one artifact in programming that people paste between languages, and the pasting is where it goes wrong: the same characters are a SyntaxError in Python, a different match in PCRE and a compile error in Go’s RE2, because the flavors are not dialects of one language but separate languages that share an ancestry. So this page runs the pattern on the same JavaScript regular-expression support your own code will use, and then spends its effort on the other half of the question, which is what the pattern means everywhere else.

Test a pattern against a text and read every match, or see what a replacement string produces - the two things a regex is usually written for.

A bare pattern, or a /pattern/flags literal. It is compiled by the engine in this browser - the same one your own code will use - and never by anything of ours.

g i m s u v y d. Write them here, or put them after the closing slash of a literal above and these are ignored.

Kept exactly as typed, line breaks included - several of the explanations below are about line breaks, tabs and non-breaking spaces.

A shape like `(a+)+$` takes exponential time, and a browser cannot interrupt a match once it has started. This page refuses that shape and explains it rather than freezing your tab.

Leave it blank for "regex-report.txt", or "replacement-result.txt" in the replace mode.

Results update as you type. Nothing leaves your device.

Matches, with their groups

2 matches

Read as a plain pattern with the flags "g", against 93 characters of subject.

Flags
g
Captures
user, host
First match at
11

The subject, with the matches marked

Ana López <⟦ana.lopez@example.com⟧> wrote on 2026-03-14:
"Ship it" - ⟦bob@danfio.com⟧, 2026-03-15

Every match is wrapped in ⟦ ⟧ - the marks are added for display and are not in your text.

Every match, with its groups

#atmatchuserhost
111ana.lopez@example.comana.lopezexample.com
267bob@danfio.combobdanfio.com

What the numbers do not say

  • `\w`, `\d` and `\b` are ASCII in JavaScript in every mode, with or without `u`: `\w+` matches `café` as `caf`, and `\bété\b` and `\bcafé\b` match nothing at all, because é is not a word character and so there is no word boundary beside it. Python 3 is the other way round - its `\w`, `\d` and `\b` are Unicode-aware by default - which is how a pattern moved between the two quietly changes meaning. `\p{Letter}` and `\p{Number}` with the `u` flag are the Unicode-aware spelling here, and `\s` is the one escape that is already Unicode-aware (it includes U+00A0 and U+3000).
  • g (global): Find every match instead of stopping at the first one.
  • The pattern is compiled by the engine in this tab - the same V8, JavaScriptCore or SpiderMonkey your own code runs on - and matched by nothing of ours, so a pattern that behaves here behaves the same way in `test`, `match`, `replace`, `split` and `search`. Everywhere else is a different language: Python, Java, .NET, Go (RE2), PCRE and grep each differ in what exists and sometimes in what `$`, `\w` or a lookaround means. The findings above name the differences this pattern actually touches.
  • A pattern is a program, and a bad one is exponential: `(a+)+$` against a long run of `a` takes about twice as long for every extra character, because the engine tries every way of splitting the run. This page refuses the one shape it can recognize with certainty and warns about nothing else, since `(a|aa)+` is just as expensive while looking perfectly ordinary - static analysis cannot see it. When a pattern becomes slow, it is almost always quantified alternation, or a nested quantifier offering the engine a choice it does not have to make.
  • Positions here are UTF-16 offsets, the same numbers `match.index` gives you, so they transfer straight into code - with one consequence worth remembering: a character outside the basic plane (an emoji, a rare script) counts as two units, and `.` without `u` matches half of one.
Compiled and matched in this browser tab: the pattern and the text never left it, there is no endpoint behind the page that could receive either, and nothing was logged.

Paste a pattern, paste the text, read every match with its groups and its position. The page names the constructs that compile in silence and mean something else (`\p{L}` without `u` matches the text "p{L}"), explains why nothing matched when nothing did, and refuses the one shape that can freeze a browser tab - because JavaScript cannot interrupt a match once it has started.

About this regex tester & replacer

Test a pattern against your own text and read every match with its groups, positions and flags - then read what the same pattern would do somewhere else. This page runs your expression on the JavaScript regular-expression support you will actually use, and spends the rest of its effort naming what differs in Python, PCRE or RE2.

Everything is matched local to the tab: the pattern and the text go to the browser’s own regular-expression support and never leave the page, which matters when the sample is a real log or a piece of customer data. Nothing is rewritten or re-implemented, so the answer here matches what your own code will see.

One shape is refused rather than run: a pattern with nested quantifiers that can backtrack without bound, because JavaScript cannot interrupt a match once it starts and the tab would freeze. The page tests matches and replacements; it does not protect a server from the same pattern.

How this regex tester & replacer works

You are testing one browser, and the page says which

A regular expression is not one language. JavaScript, Python, Java, .NET, PCRE, Perl, PHP, Ruby, grep, Go’s RE2 and Rust’s regex crate all implement their own pattern syntax, and the differences are not cosmetic: some constructs exist in one and not another (atomic groups, possessive quantifiers, `\K`, `[[:alpha:]]`), and some constructs mean different things in two of them (`$` before a trailing newline, `\w` for accented letters, what a lookaround is allowed to see).

Your pattern runs on this browser’s own JavaScript regular-expression support, in your own tab, so a pattern that works here works the same way in `test`, `match`, `replace`, `split` and `search` in your own code. Nothing is simulated or translated, and the report says which features this browser supports, so a bug report can name the browser it came from.

Where the browser cannot help is the other direction. The panel names the constructs that another flavor has, and says which flavor has them, so a pattern ported from Python or written against PCRE documentation stops being a mystery. That is the part a tester usually leaves out, and it is the part that costs an afternoon.

The flags, and the three that surprise people

The letters after the closing slash are not decoration; they change what a pattern means. `g` decides whether you get one match or all of them, and without it `replace` changes one occurrence - the most common surprise in JavaScript strings. `i` folds case, using Unicode *simple* folding, which is why `ß` still does not match `ss` and a class written as `[a-z]` is still only 26 ASCII letters. `m` makes `^` and `$` match line by line instead of at the two ends of the whole string, and `s` lets `.` match a line break - without it `.` stops at a newline, which is the reason a pattern that works on one line matches nothing in a file.

The three that surprise people most are about characters rather than flags. First: `\w`, `\d` and `\b` are ASCII in every mode, so `\w+` matches `café` as `caf`, and `\bété\b` matches nothing at all, because é is not a word character and `\b` wants a word character on both sides. `\p{Letter}` with `u` is the Unicode-aware version, and Python 3 is the other way round by default, which is where the confusion comes from.

Second: `$` without `m` matches only at the very end of the string, so `/abc$/` does not match "abc\n" - Python and Perl both match there. Third: `y` (sticky) is not "start at position 0 and then search on"; it matches at `lastIndex` and nowhere else, which on a fresh pattern is a single position. That flag is usually what somebody wants when a `^` inside a loop behaves strangely.

Groups, positions, and the replacement syntax

A group captures what it matched, and carries a name if you give it one: `(?<user>[\w.]+)` is group 1 and also `user`. The `d` flag adds `match.indices`, the start and end of the whole match and of every group - which is how you learn *where* a group matched rather than only what it captured, and it is the flag to turn on when the right text matches in the wrong place. Positions here are UTF-16 offsets, the same numbers `match.index` gives you, so an emoji counts as two units and every number transfers straight into code.

The replacement string has its own small language, and it is where two habits collide. In JavaScript `$1` is group 1, `$&` is the whole match, `$<name>` is a named group, `$$` is a literal dollar, `` $` `` is everything before the match and `$'` is everything after it. In sed, grep and Perl a backreference is `\1` and the whole match is `&`; in .NET the whole match is `$0`. JavaScript leaves all of those alone, so a replacement copied from another language produces literal text and no error at all.

One distinction is worth more than the rest because it looks like nothing: a reference to a group that does not exist is copied into the output as text (`$2` stays `$2`), while a reference to a group that exists but did not take part in this particular match becomes an empty string. Two replacement strings that differ by one character can therefore behave completely differently, and neither of them throws.

Why a pattern can hang the tab, and what this page does instead

A regular expression is a program, and a badly written one is slow in a way that surprises people who are used to loops. `(a+)+$` against a long run of `a` followed by one character that does not match makes the engine try every way of splitting that run in two, then in three, then in four: each extra character doubles the work, so a thirty-character input is about a billion attempts. Nothing is broken and there is no error - the tab simply stops responding.

The engines that avoid this are the ones that gave up features to do it. RE2, Go’s `regexp` and Rust’s regex crate match in time proportional to the input by refusing backreferences and lookarounds, which is why the same pattern can be linear in Go and catastrophic in JavaScript. In a backtracking engine the fix is to write the pattern so that every repetition consumes a character it cannot give back - matching a run once and testing what follows it, rather than nesting one quantifier inside another.

JavaScript cannot interrupt a running match: there is no timeout to set and no way to abort a `RegExp` mid-flight, so "run it and see" answers the wrong question. This page therefore refuses the one shape it can recognize with certainty - a quantifier applied to a group that already contains an unbounded quantifier - points the caret at it, explains it, and offers a switch for the case where your input really is a few dozen characters. It also says out loud what a static check cannot see: `(a|aa)+` is just as expensive and looks perfectly innocent.

Worked examples

Each example below was run through the calculator on this page when the site was built, so the numbers match what you see when you enter the same inputs.

Two addresses, two named groups

What to do: Test a pattern: matches, groups and where they are · Regular expression: (?<user>[\w.%+-]+)@(?<host>[\w.-]+\.[A-Za-z]{2,}) · Flags: g · Text to search: Ana López <ana.lopez@example.com> wrote on 2026-03-14: "Ship it" - bob@danfio.com, 2026-03-15

Matches, with their groups

2 matches

Read as a plain pattern with the flags "g", against 93 characters of subject.

Flags: g · Captures: user, host · First match at: 11

Both matches come back with `user` and `host` separated, which is what a named group is for: the column headings in the result say `user` and `host` instead of `1` and `2`, and the replacement can say `$<user>` rather than `$1`.

The dialect trap that compiles silently

What to do: Test a pattern: matches, groups and where they are · Regular expression: \p{L}+ · Flags: g · Text to search: café 日本語

No match

nothing matched

Read as a plain pattern with the flags "g", against 8 characters of subject.

Flags: g · Captures: none · First match at: nowhere

Without the `u` flag this reads `\p` as the plain letter `p`, so it looks for the text "p{L}" and finds nothing - no error, no match and no clue. Add `u` and the same pattern matches every letter, accents and Chinese included.

Finding and replacing with a named group

What to do: Find and replace: $1, $<name>, $& and the rest · Regular expression: (?<user>[\w.%+-]+)@(?<host>[\w.-]+\.[A-Za-z]{2,}) · Flags: g · Text to search: ana.lopez@example.com and bob@danfio.com · Replace each match with: $<user> at $<host>

The replacement, applied

2 replacements

Every match was replaced, because the pattern has `g`.

Flags: g · Matches found: 2 · Replacements: 2

With `g` both addresses are rewritten. Delete the `g` and only the first one changes, which is the difference between `replace` and `replaceAll` made visible in one keystroke.

Frequently asked questions

Is this the same regex as Python, Java or PCRE?

No, and that is the point of the page. It is JavaScript’s own engine (ECMA-262), which is what your browser and Node run. Python’s `re`, Java’s `Pattern`, .NET, PCRE, Perl, Ruby and PHP each implement their own syntax, and Go’s RE2 and Rust’s regex crate implement a deliberately smaller one. Some constructs exist in only one of them - atomic groups `(?>...)`, possessive quantifiers `a++`, `\K`, `[[:alpha:]]`, the free-spacing `x` flag - and some constructs mean different things (`$` before a trailing newline, `\w` for accented letters, `$0` in a replacement). The panel names the differences your own pattern touches, which is more useful than a compatibility table.

Why does my pattern match here but not in Python?

Usually one of four things. `\w`, `\d` and `\b` are ASCII in JavaScript and Unicode-aware in Python 3, so a pattern meant for accented or Chinese text behaves differently in the two. `$` matches before a final newline in Python and only at the very end in JavaScript. Named groups are written `(?P<name>...)` in Python and `(?<name>...)` here. And Python’s `re.match` anchors at the start while JavaScript’s `test` searches anywhere, so the same pattern finds a match in one language and misses it in the other. Each of those is stated plainly on this page rather than left to be discovered.

What is catastrophic backtracking, and how do I avoid it?

It is what happens when a pattern gives the engine a choice it can take in exponentially many ways. `(a+)+$` on a long run of `a` is the classic: the inner `+` and the outer one can split the run into two, three or four pieces, and every split is tried before the pattern gives up. `(a|aa)+` is just as bad while looking innocent. The fix is to make each repetition consume something it cannot give back: match the run once and test what follows it, prefer a character class to a wildcard where you can, and remember that the *failing* input is the expensive one - the pattern is fast on text it matches. RE2, Go and Rust’s regex engine avoid the problem entirely by giving up backreferences and lookarounds; a browser cannot, so the shape has to be recognized instead.

Why does a dot not match my newline?

By default `.` matches any character except a line terminator, and that is true in JavaScript, Python, Java, .NET and PCRE alike - so a pattern like `.*` written on one line finds nothing in a file, and the usual reaction is to blame the text. The `s` flag (dotAll) changes it, and `[\s\S]` is the portable spelling that works in every flavor without a flag. The page offers this as an explanation in place of a bare "no match" whenever the pattern uses `.` and the subject has more than one line, because that is almost always the answer.

Why does \w not match accented letters or Chinese characters?

Because `\w` means `[A-Za-z0-9_]` in JavaScript, in every flag combination - adding `u` does not change it, which is the part people expect it to fix. `café` is `caf` to `\w+`, and `\bcafé\b` matches nothing at all, because there is no word boundary beside a letter the engine does not count as a word character. The Unicode-aware spelling is `\p{Letter}` with the `u` flag, and `\p{Number}` for digits. Python 3 is Unicode-aware by default, which is why a pattern moved between the two languages quietly changes meaning.

Why did only the first match get replaced?

Because the pattern has no `g` flag. `String.prototype.replace` with a regular expression replaces only the first occurrence, while the same method with a *string* as its search value replaces all of them - so the two halves of one method disagree, and that is where the confusion comes from. The fixes are the `g` flag and `replaceAll`, which refuses a non-global pattern rather than doing something surprising. The replace mode here uses your flags exactly as written and shows the difference rather than smoothing it over.

What does the d flag add, and what does $& mean in a replacement?

`d` (indices) adds `match.indices`: the start and end of the whole match and of every group, which is the only way to learn where a group matched - the report prints it as a range table. In a replacement string `$&` is the whole match, `$1` and `$<name>` are groups, `$$` is a literal dollar, `` $` `` is everything before the match and `$'` is everything after it. Two of the things people write are not references at all: `$0`, which .NET uses for the whole match and JavaScript leaves as text, and `\1`, which sed, grep and Perl use and JavaScript leaves as two characters.

Is it safe to paste a pattern and my own text here?

Yes, and that is the reason for a page that runs in the browser rather than on a server. Everything is compiled and matched by JavaScript in this tab: there is no endpoint behind the page that could receive your pattern, your subject or your replacement, nothing is logged, and no request is made while you type. The static HTML you first loaded already contains a real result, which would not be possible if the matching happened anywhere else. The subject is searched up to 100,000 characters and at most 500 matches are listed, and the panel says when either limit is reached.

Assumptions and sources

  • Patterns run on your browser's own regular-expression support, so the syntax and the eight flags are JavaScript's (ECMA-262) and behave the same in your own code.
  • Nothing is executed: a pattern that could grow exponentially is stopped before it runs, and you can deliberately say run it anyway.
  • The pattern and the text stay in your browser; nothing is uploaded.

Last reviewed 2026-09-16. Runs locally on your device; keep backups; no financial advice applies. Read the full disclaimer.

Where a figure on this page comes from an official agency — HUD, IRS, FICO or a state body — the site keeps a dated snapshot of the source page in its sources library, and a scheduled job re-checks that snapshot, so the number can be traced back to the document it came from.