UUID Studio

Regex Tester

Test JavaScript RegExp patterns.

  • 🔒 No data stored or uploaded
  • âš¡ 100% client-side
  • 🆓 Free, no account

Need more than one tool at a time? Open the full Workbench - or press Ctrl+K to jump to any tool.

Create or edit JSON here (syntax colors), then format, validate, or convert - same as MongoDB $binary UUID blobs on the Convert tab once detected.

New document

About Regex Tester

A regular expression tester earns its place by showing you the match, the capture groups and the position of each - because the gap between an expression that looks right and one that matches what you meant is where the time goes.

The flags do most of the work and are worth being deliberate about. g finds every match rather than the first; i makes matching case-insensitive; m changes ^ and $ to match at line boundaries instead of only string boundaries; s makes . match newlines, which it otherwise does not; and u enables full Unicode handling so that astral characters and \p{...} property escapes work at all.

The single most common cause of a slow or hung pattern is catastrophic backtracking. Nested quantifiers over overlapping character classes - the classic (a+)+$ shape, or (\w+\s?)*$ - make the engine try exponentially many ways to split the input before it concludes there is no match. On a 30-character non-matching string that can take seconds; on a user-supplied input it is a denial-of-service vector, which is exactly what a ReDoS advisory means.

Dialects differ more than people expect. JavaScript has no lookbehind in older engines (it landed in ES2018 and is still missing from some Safari versions), Python uses (?P<name>...) for named groups where JavaScript uses (?<name>...), and POSIX tools like grep and sed need -E for extended syntax and support neither lookaround nor \d. A pattern copied between them frequently needs adjusting.

Matching runs in your browser using its own regex engine, so what you see here is exactly what JavaScript will do, and nothing is transmitted.

How to use the Regex Tester

  1. Enter the pattern and the test text. Set the flags you actually need rather than reaching for g and i by habit.
  2. Read the matches and capture groups, and check the positions - an off-by-one usually means an unintended greedy quantifier.
  3. If a pattern seems to hang, look for nested quantifiers; that is catastrophic backtracking, not a slow machine.
  4. Confirm the syntax is supported in your target language - lookbehind, named groups and Unicode escapes all vary.

Examples

  • Email-ish
    [\w.+-]+@[\w.-]+\.[a-z]{2,}

Regex Tester in code

The same operation this tool performs, in the languages you are most likely to need it.

JavaScript
const re = /(?<year>\d{4})-(?<month>\d{2})-(?<day>\d{2})/u;
const m = "due 2026-08-31".match(re);
m.groups.year;      // "2026"
m.index;            // 4

// All matches with positions - matchAll needs the g flag
for (const m of text.matchAll(/\b\w+@\w+\.\w+\b/g)) {
  console.log(m[0], m.index);
}

// A lastIndex trap: a /g or /y regex object is STATEFUL.
const g = /a/g;
g.test("a");   // true
g.test("a");   // false  <- lastIndex advanced
// Use a fresh regex, or reset g.lastIndex = 0, or use matchAll.
Python
import re

# Named groups use (?P<name>...) in Python
m = re.search(r"(?P<year>\d{4})-(?P<month>\d{2})", "due 2026-08")
m.group("year")     # '2026'
m.span()            # (4, 11)

# re.finditer gives positions; re.findall gives only the text
for m in re.finditer(r"\b\w+@\w+\.\w+\b", text):
    print(m.group(), m.start())

# Compile once if you use it in a loop
EMAIL = re.compile(r"^[^@\s]+@[^@\s]+\.[a-z]{2,}$", re.I)

# re.VERBOSE lets you comment a complex pattern
DATE = re.compile(r"""
    (?P<year>\d{4}) -      # four-digit year
    (?P<month>\d{2})       # two-digit month
""", re.X)
Command line - dialect differences
# Basic regex (BRE): + and ? and | are literal unless escaped
grep 'colou\?r' file.txt

# Extended regex (ERE) - almost always what you want
grep -E 'colou?r|gr[ae]y' file.txt

# Perl-compatible: \d, \w, lookaround. GNU grep only.
grep -P '(?<=id=)\d+' file.txt

# sed needs -E too, and its own escaping
sed -E 's/([0-9]{4})-([0-9]{2})/\2\/\1/' file.txt

# ripgrep uses Rust regex: fast, no backtracking, so no ReDoS -
# but also no lookaround by default.
rg '\b\w+@\w+\.\w+\b' .
Catastrophic backtracking - what to avoid
// DANGEROUS: nested quantifier over overlapping classes.
// On a long non-matching string this takes exponential time.
const bad = /^(\w+\s?)*$/;
// bad.test("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaa!")  <- hangs

// Safe rewrite: make the alternatives non-overlapping
const good = /^\w+(?:\s\w+)*$/;

// Other shapes to watch for:
//   (a+)+      (a|a)*      (.*)*      ([a-z]+)*

// Practical defences:
//  - prefer a real parser for structured input (URLs, emails, dates)
//  - cap input length before matching untrusted text
//  - use a linear-time engine (Go's regexp, Rust's regex, RE2)
//  - lint with eslint-plugin-security or a ReDoS checker in CI

When you need this

  • Working out why a validation pattern rejects input you believe is valid.
  • Building a capture pattern to pull fields out of a log line.
  • Testing a find-and-replace before running it across a codebase.
  • Checking that a pattern behaves the same in JavaScript as it does in Python or grep.
  • Confirming a suspected ReDoS pattern really does backtrack catastrophically.

Common problems and what causes them

Catastrophic backtracking (ReDoS)
Nested quantifiers over overlapping character classes - (a+)+, (\w+\s?)* - make the engine try exponentially many splits before failing. On untrusted input this is a denial-of-service vector. Rewrite so alternatives cannot overlap, cap input length, or use a linear-time engine like RE2 or Rust's regex.
A /g regex object carrying state between calls
test() and exec() on a global regex advance lastIndex, so calling test() twice on the same string returns true then false. Use a fresh literal each time, reset lastIndex, or use matchAll.
Dot not matching newlines
. excludes line terminators unless the s (dotAll) flag is set. A pattern that works on one line silently fails across a multi-line payload.
^ and $ anchoring to the string, not the line
Without the m flag they match only at the very start and end of the whole input. With it they match at every line boundary - which is usually what you want when processing a file.
Greedy quantifiers overshooting
In <.*>, the .* consumes to the last > on the line, not the first. Use the lazy form <.*?> or a negated class <[^>]*>, the last of which is both clearer and faster.
Unescaped metacharacters in interpolated input
Building a pattern from user input without escaping . * + ? ( ) [ ] { } | ^ $ \ either breaks the pattern or creates an injection. Escape with a helper, or use a literal string search instead.
Validating emails or URLs with a regex
A fully RFC-compliant email pattern is thousands of characters long and still does not tell you the address exists. Use a simple sanity check plus a confirmation email, and a real URL parser for URLs.

FAQ

Why is my regex so slow, or hanging entirely?
Almost certainly catastrophic backtracking from nested quantifiers such as (a+)+ or (\w+\s?)*. The engine explores exponentially many ways to split the input before giving up. Rewrite so the alternatives do not overlap, and cap the length of untrusted input.
What does the m flag actually change?
It makes ^ and $ match at line boundaries rather than only at the start and end of the whole string. It does not affect what . matches - that is the separate s (dotAll) flag.
Why does test() return true and then false for the same string?
The regex has the g flag, which makes it stateful: lastIndex advances after a match and the next call resumes from there. Use a non-global regex for tests, reset lastIndex, or switch to matchAll.
Does my pattern work the same in every language?
Often not. Named groups are (?<n>...) in JavaScript and (?P<n>...) in Python; lookbehind is unsupported in older JavaScript engines; POSIX grep needs -E and has no \d or lookaround; and Go and Rust use linear-time engines with no backtracking and therefore no lookaround at all.
Should I use a regex to validate an email address?
Only as a rough sanity check - something contains an @ with text either side and a dot in the domain. Full RFC 5322 compliance is impractical and still proves nothing about deliverability. Send a confirmation email; that is the real validation.