Regex Tester

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).

/ /
No pattern yet
Test String
Matches

1 Regex Tester example

Named groups in action

1
2
3
Pattern:  (?<user>[\w.+-]+)@(?<domain>[\w-]+(?:\.[\w-]+)+)
Match 1:  billing@acme-corp.com
          user = billing, domain = acme-corp.com

2 Cheat sheet

Characters

  • .: any character except a newline
  • \d \w \s: digit, word char, whitespace
  • \D \W \S: the opposite of each
  • [abc]: one of a, b or c
  • [^abc]: anything but those
  • [a-z0-9]: a range

Quantifiers

  • *: zero or more
  • +: one or more
  • ?: zero or one
  • {3} {2,} {2,5}: exact, at least, range
  • +? *?: lazy, stop at the first chance

Anchors

  • ^ $: start and end
  • \b: word boundary
  • (?=x): followed by x
  • (?!x): not followed by x
  • (?<=x): preceded by x

Groups

  • (abc): capture
  • (?:abc): group without capturing
  • (?<name>abc): named capture
  • a|b: either side
  • \1: whatever group 1 matched

Flags

  • g: every match, not only the first
  • i: ignore case
  • m: ^ and $ match each line
  • s: the dot also matches newlines
  • u: full Unicode, enables \p{L}

Escaping

  • Escape . + * ? ^ $ ( ) [ ] { } | \
  • Inside [ ] only ^ - ] \ matter
  • \/ only when the delimiter is /

3 Two things that will bite you

Greedy by default

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.

Catastrophic backtracking

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.

4 This is the JavaScript engine

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.

How to use the Regex Tester tool

  1. Type the pattern into the box between the two slashes. Leave out the slashes themselves, because the page already draws them.
  2. Set the flags in the small box after the pattern. The tool always adds g, because without it you would only see the first match.
  3. Paste the text to test against into the left panel. Matches are highlighted as you type.
  4. Read the Matches panel for the details: each match with its position, every numbered capture group, and any named groups.
  5. A red message under the pattern box means the pattern itself is invalid. It shows the browser's own error text, such as "nothing to repeat".

How to use a regular expression in JavaScript, PHP and Python

JavaScript

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

<?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 #.

Python

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))

Common errors and how to fix them

The pattern matches far more than expected
Quantifiers are greedy: .+ matches as much as it can and backs off only as far as it must. Add a question mark for the lazy version (.+?), or replace the dot with a character class that cannot cross the boundary, such as [^<]+.
SyntaxError: Invalid regular expression: nothing to repeat
A quantifier has nothing before it. This is usually an unescaped + or ? that was meant as a literal character. Escape it as \+ or \?.
The page or the server hangs on a long input
This is catastrophic backtracking. A quantifier inside a quantifier, like (a+)+ or (\s*\w+)*, has an exponential number of ways to split a string that fails to match. Restructure the pattern so each character can be consumed only one way, or add an anchor that fails early.
^ and $ do not match at each line
Add the m flag. Without it, ^ and $ mean the start and end of the whole string, not of each line.
The pattern works here but not in PHP or Python
Regex flavors differ. JavaScript has no \A, \z or possessive quantifiers, Python spells named groups (?P<name>...), and Go's RE2 has no backreferences or lookaround at all. Test the tricky parts in the target language.

Good to know

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.

Specifications and references

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.

Frequently Asked Questions

Which regex flavor does this use?

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.

What do the flags do?

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.

Why does my pattern match nothing?

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.

Can I see the capture groups?

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.

Does the text I paste get sent anywhere?

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.

Why did the page freeze on a long input?

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.

Related

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.

More Text & Content Tools

Browse all text & content tools

Copied to clipboard!