Regex Tester
Write a pattern, watch it match your text as you type, and see exactly what every token does. Capture groups, a replace preview, and a portability check for PCRE and Python.
Pattern
Your regular expression
Write the pattern body only — no surrounding slashes. Everything updates as you type.
Changes the portability check only — matching always runs on your browser's JavaScript engine.
Presets
Test text
Text to match against
Paste real data — a log line, a config, a chat export. Nothing leaves your browser.
794 characters · 14 lines
Wespner ops log — node eu-fra-03 Ticket #4821 opened by [email protected], escalated to [email protected]. Billing contact: [email protected] (invoice WX-2026-0917). Panel: https://panel.wespner.eu/server/eu-fra-03/console Mirror: http://mirror.wespner.eu:8080/backups/latest.tar.zst Node IP 185.199.110.153, gateway 10.0.0.1, bad entry 999.1.1.1 in the allowlist. Server UUID 9f2c1a7e-3b44-4d8e-9c21-7a5f0b6e2d13 (instance 550e8400-e29b-41d4-a716-446655440000). Chat export: [12:04] Steve_84: this build is hell to maintain, honestly [12:05] Alex_MC: rotate the panel password and open a shell on the class-4 node [12:06] Steve_84: damn, the ass-end of that config is unreadable [12:07] Notch_Fan: <img src=x onerror=alert(1)> nice try [12:08] Alex_MC: harassment report filed, see /docs/rcon
Capture groups
Every match, with its groups and positions
Numbered groups come from plain ( ). Named groups come from (?<name>…) and are listed separately.
Enter a pattern to see matches and their capture groups.
Explain mode
What each piece of the pattern actually does
Read top to bottom — that is the order the engine reads it. Indentation shows nesting inside groups.
(?<user>Named capturing group “user”, also group number 1. Refer to it as $<user> in a replacement, or \k<user> in the pattern.[\w.+-]Any single character that is a word character (letter, digit or underscore), “.”, “+” or “-”.+Repeats the previous token one or more times. Greedy: it takes as much as it can, then hands characters back one at a time if the rest of the pattern fails.)Closes the group opened above.@The character “@”, matched literally.(?<domain>Named capturing group “domain”, also group number 2. Refer to it as $<domain> in a replacement, or \k<domain> in the pattern.(?:Non-capturing group: groups tokens together for a quantifier or an alternation without storing what it matched, so it does not consume a group number.[\w-]Any single character that is a word character (letter, digit or underscore) or “-”.+Repeats the previous token one or more times. Greedy: it takes as much as it can, then hands characters back one at a time if the rest of the pattern fails.\.The character “.”, matched literally. The backslash strips any special meaning it would otherwise have.)Closes the group opened above.+Repeats the previous token one or more times. Greedy: it takes as much as it can, then hands characters back one at a time if the rest of the pattern fails.[a-z]Any single character that is anything from “a” to “z”.{2,}Repeats the previous token at least 2 times, with no upper limit. Greedy: it takes as much as it can, then hands characters back one at a time if the rest of the pattern fails.)Closes the group opened above.giFlags: g finds every match instead of stopping at the first; i ignores letter case.Replace
Preview a find-and-replace
Use $1, $2 for numbered groups, $<name> for named ones, $& for the whole match and $$ for a literal dollar sign.
Result
Enter a pattern to preview a replacement.
Honest note
This page does not run PCRE or Python
There is only one regex engine here: the one built into your browser, which implements JavaScript's flavour. Selecting PCRE or Python does not switch engines and cannot — no PCRE or CPython runs in this tab.
What the flavour setting does instead is check your pattern for constructs that are written differently, behave differently, or simply do not exist in the flavour you picked. Treat the matches above as JavaScript results, and the list on the right as "what will bite you when you paste this into PHP or Python".
Equivalent call
const re = /(?<user>[\w.+-]+)@(?<domain>(?:[\w-]+\.)+[a-z]{2,})/gi;
for (const m of text.matchAll(re)) console.log(m);A literal is fine when the pattern is fixed. Building one from a variable needs new RegExp(src, flags) — and the source escaped first.
Portability check
Portability to JavaScript
Heuristic — the pattern is scanned as text, so a construct written inside a character class may still be reported.
Cheat sheet
Greedy vs. lazy
The single most common reason a pattern "matches too much".
<.*> against <b>bold</b> matches the whole string, not <b>. * and + are greedy: they grab everything they can and only hand characters back when the rest of the pattern fails.
Add ? to make a quantifier lazy — <.*?> stops at the first >. Usually better still: say what you actually mean, <[^>]*>, which cannot cross the delimiter at all and does not backtrack.
Cheat sheet
Anchors and boundaries
^ and $ are the start and end of the string — until you add the m flag, when they become the start and end of each line. Forgetting m on a multi-line config file is why a per-line pattern silently matches nothing.
\b is a zero-width word boundary, not a character. \bcat\b matches cat but not concatenate. Inside a character class [\b] means backspace instead — a genuine trap.
A pattern that must match the entire input needs both anchors. ^\d+ alone happily matches the start of 123abc.
Cheat sheet
Catastrophic backtracking
Nested quantifiers where the inner and outer parts can match the same text — (a+)+$, (\w+\s?)*$, (\d+)*x — give the engine exponentially many ways to split the input. On a failing string each extra character roughly doubles the work, so 30 characters can take minutes.
The fix is to remove the ambiguity: (a+)+ becomes a+; (\w+\s?)* becomes \w+(?:\s\w+)*. Where your engine supports them, an atomic group (?>…) or a possessive quantifier a++ forbids the backtracking outright — PCRE and Python 3.11+ have both, JavaScript has neither.
This tester runs matching in a Web Worker with a time limit, so a runaway pattern aborts instead of freezing the tab. Your production server has no such safety net.
Cheat sheet
Word filters and the Scunthorpe problem
A word list without boundaries matches inside innocent words. The canonical case is the English town of Scunthorpe, whose residents were blocked from signing up for services for years because of the four letters in the middle of its name.
Closer to home: hell matches shell, ass matches class and password, doc matches document. Wrapping the alternation in \b(?:…)\b fixes most of it. It will not fix deliberate evasion (h e l l, h3ll), and chasing that with regex produces more false positives than it prevents — which is why real moderation pairs a small, conservative regex with human review.
Cheat sheet
Escaping, classes and the dot
Inside [ ] most metacharacters lose their power: [.+*] is three literal characters. Only ^ (first position), - (between characters) and the backslash stay special, and ] must be escaped.
The dot means "any character except a line break" until you add s. In an IP or version pattern an unescaped dot matches anything — 1.2.3.4 also matches 1x2y3z4.
When you build a pattern from user input, escape it first. In JavaScript: s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"). In PHP preg_quote(), in Python re.escape().
Cheat sheet
Groups you probably want to be non-capturing
Every ( ) stores its match and renumbers everything after it. If you only need grouping for an alternation or a quantifier, use (?:…) — it keeps your $1 stable when you later add a group in front of it.
Named groups (?<user>…) survive reordering entirely, and read far better in a replacement string: $<user> instead of $3. Python spells them (?P<user>…).
Running your own game server?
Wespner game servers with DDoS protection, NVMe drives and activation within minutes.