Test a regular expression against sample text and
see every match highlighted as you type, with each match's
position and capture groups listed on the right. A cheat sheet
below covers the syntax that is easy to mix up, such as
\b (word boundary) and \B (not a word
boundary).
Pattern: (?<user>[\w.+-]+)@(?<domain>[\w-]+(?:\.[\w-]+)+)
Match 1: billing@acme-corp.com
user = billing, domain = acme-corp.com Quantifiers take as much as they can. For example, <.+> on a line of HTML matches everything from the first bracket to the last. Add a question mark, <.+?>, and the match stops at the first closing bracket instead.
A quantifier inside a quantifier, like (a+)+b, takes exponential time on a string that does not match, because the engine tries every way of splitting it. It passes tests on short inputs and can block a server thread on a long one. Rewrite it so each character has only one way to be consumed.
Test the tricky parts in the language you ship to.
This page uses the RegExp engine built into your browser, and
other flavors differ in ways that matter. PCRE, used by PHP's
preg functions, supports possessive quantifiers and atomic
groups that JavaScript has no syntax for. Python's re spells
named groups (?P<name>...) rather than
(?<name>...). Go's RE2 has no backreferences
and no lookaround at all, by design, because it guarantees
matching in linear time.
const re = /(?<user>[\w.+-]+)@(?<domain>[\w-]+\.[\w.]+)/g;
const text = "billing@acme.com and finance+archive@acme.co.uk";
// Every match, with groups
for (const m of text.matchAll(re)) {
console.log(m[0], m.groups.user, m.groups.domain);
}
// Just a yes or no
console.log(/^\d{4}-\d{2}-\d{2}$/.test("2026-09-10")); // true
// Replace with a callback
console.log(text.replace(re, (_, user) => user + "@example.com")); A regex with the g flag keeps state in lastIndex. If you reuse the same object across calls to test(), the results alternate between true and false. Use matchAll() or create a fresh regex for each check.
<?php
$re = '/(?P<user>[\w.+-]+)@(?P<domain>[\w-]+\.[\w.]+)/';
$text = 'billing@acme.com and finance+archive@acme.co.uk';
preg_match_all($re, $text, $matches, PREG_SET_ORDER);
foreach ($matches as $m) {
echo $m[0], ' -> ', $m['user'], ' at ', $m['domain'], PHP_EOL;
}
// preg_match returns 1, 0 or false. false means the pattern failed,
// which is not the same as "no match", so check it explicitly.
if (preg_match('/^\d{4}-\d{2}-\d{2}$/', '2026-09-10') === 1) {
echo "looks like a date\n";
} PHP accepts named groups as (?P<name>...) or (?<name>...). The delimiter is part of the string, so a pattern containing / needs each slash escaped or a different delimiter such as ~ or #.
import re
pattern = re.compile(r"(?P<user>[\w.+-]+)@(?P<domain>[\w-]+\.[\w.]+)")
text = "billing@acme.com and finance+archive@acme.co.uk"
for m in pattern.finditer(text):
print(m.group(0), m.group("user"), m.group("domain"))
# fullmatch anchors both ends without needing ^ and $
print(bool(re.fullmatch(r"\d{4}-\d{2}-\d{2}", "2026-09-10")))
# Always use a raw string, or every backslash needs doubling
print(re.sub(pattern, lambda m: m.group("user") + "@example.com", text)) Use a regex for a token and a parser for a grammar. Matching an email address well enough for a form is a job for a pattern. HTML, JSON and anything else with nesting is not, because a regex cannot track how deep it is. For those inputs, use a real parser.
Anchoring is a security concern as often as a correctness one. For example, /example\.com/ matches evil-example.com.attacker.net, which is the bug behind many open-redirect and CORS mistakes. Anchor with ^ and $, or parse the URL and compare the host.
This tool follows the specifications below. Read them when you need the exact normative wording, for example to settle an edge case between two implementations.
JavaScript's, the RegExp engine built into your browser. Most of the syntax is shared with PCRE, but a few things differ: JavaScript has no \A or \z anchors and no possessive quantifiers, and lookbehind needs Safari 16.4 or later (Chrome and Firefox have had it for years). If the pattern is headed for PHP or Python, test the edge cases there too.
g finds every match instead of stopping at the first, i ignores case, m makes ^ and $ match at each line break, s lets the dot match newlines, and u turns on full Unicode handling so \p{...} property escapes work.
Usually an unescaped character. A dot, plus, question mark, bracket or slash has a special meaning in a pattern, so put a backslash in front when you want the literal character. For example, 1.5 matches 105, while 1\.5 matches only 1.5. When the pattern itself is invalid, the red error line under the input says so.
Yes. The Matches panel lists each match with its position (the character index where it starts), the content of every numbered group, and named groups if you used (?<name>...). It shows the first 200 matches.
No. The pattern runs against your text in the browser's own RegExp engine and nothing is uploaded, so log excerpts and customer data stay on your machine.
The usual cause is catastrophic backtracking. Nested quantifiers such as (a+)+ give the engine an exponential number of ways to split a string that does not match, and it tries them all. Rewrite the pattern so no quantifier sits inside another, and it finishes in milliseconds.
A working pattern often goes straight into find and replace, and the diff checker confirms it changed only what you meant. For rewriting names in bulk there is the case converter, and for reading structured input first, the JSON formatter.