Skip to content

Regex Tester

A regex tester runs a pattern against sample text and shows you exactly what it matched. This one uses your browser's own JavaScript engine — the same RegExp your code will use — with live highlighting, a capture-group table, a match count, and guards against patterns that would otherwise hang the tab.

Flags

0 characters. Capped at 20,000 characters. Runs locally; nothing is uploaded.

Pattern /\b\d{1,3}(?:\.\d{1,3}){3}\b/g compiles. Add a test string above to run it.

Which regex dialect this is

This page compiles your pattern with new RegExp(pattern, flags), so what you see is precisely how it behaves in JavaScript, TypeScript, and Node. That matters because regex is not one language. PCRE (PHP, and grep -P), POSIX ERE (grep -E, sed -E), Python's re, and Go's RE2 all differ in ways that will silently change your results.

  • Lookbehind — (?<=…) — works in modern JavaScript, PCRE, and Python, but not in Go's RE2 or in grep -E.
  • Atomic groups and possessive quantifiers — (?>…), a++ — exist in PCRE and not in JavaScript. This is unfortunate, because they are the cleanest defence against catastrophic backtracking.
  • `\d` and `\w` are ASCII-only in JavaScript by default. With the u flag and \p{…} you get Unicode properties: \p{L} for any letter, \p{N} for any number.
  • RE2 (Go, and Google's tooling) has no backtracking at all, which makes it immune to the exponential blowup described below — but it also has no backreferences or lookaround.
  • POSIX ERE has no \d, no non-greedy quantifiers, and no lookaround. grep -E '\d+' does not do what you expect; grep -E '[0-9]+' does.

So a pattern verified here is verified for JavaScript. Before pasting it into a grep command or a Go service, check that every construct exists in that dialect.

The flags

FlagNameEffect
gglobalFind every match rather than stopping at the first. Without it, String.replace replaces only once.
iignoreCaseCase-insensitive matching.
mmultilineMakes ^ and $ match at line breaks instead of only at string boundaries.
sdotAllMakes . match newlines too. Essential for matching across lines in a log or an HTML blob.
uunicodeCorrect handling of code points above the BMP and enables \u{…} and \p{…}.
ystickyMatches only at lastIndex — used by tokenizers, rarely useful interactively.
Read the full explanation of regex tester results ↓

The g flag has a sharp edge worth knowing about. A global regex object carries mutable state in its lastIndex property, so calling .test() twice on the same regex with the same string alternates between true and false. Reusing a module-level const RE = /x/g across requests is a genuine and very confusing bug source. Either create the regex where you use it, or reset lastIndex to 0 before each run.

The m flag is the one people reach for when they mean s. m changes what ^ and $ anchor to; s changes whether . crosses a newline. If your pattern fails on multi-line input, you almost certainly want s.

Capture groups

Parentheses do two jobs: they group for quantifiers, and they capture for extraction. When you only need the grouping, use a non-capturing group (?:…). This is not just tidiness — every capturing group has a cost, and more importantly it shifts the numbering of every group after it, which quietly breaks code that reads $2.

Named groups solve that problem properly: (?<year>\d{4})-(?<month>\d{2}) gives you match.groups.year regardless of position. This tool shows both the numbered and the named columns in its results table, so you can see exactly what your code will receive.

A group that did not participate in the match is undefined, not an empty string. /(a)|(b)/ matching against "b" gives $1 === undefined and $2 === "b". Code that does match[1].trim() on an optional group throws, and the table on this page distinguishes undefined from empty for exactly that reason.

Greedy, lazy, and the backtracking that follows

*, +, and ? are greedy: they consume as much as possible, then give characters back until the rest of the pattern can match. Adding ? makes them lazy — .*? takes as little as possible and expands only as needed. The classic demonstration is matching an HTML tag: <.*> against <b>bold</b> matches the entire string, because the greedy .* runs to the end and backtracks only to the *last* >. <.*?> matches just <b>.

Better than either is to match precisely what you mean: <[^>]*> cannot overshoot at all, needs no backtracking, and is faster. As a general rule, a negated character class beats a lazy quantifier whenever you can express the stop condition as "any character except X".

Catastrophic backtracking, and why this tool has caps

JavaScript's regex engine is a backtracking engine, and backtracking can go exponential. The canonical example is ^(a+)+$ tested against a string of 30 a characters followed by a !. There is no match, but the engine must try every way of partitioning those 30 characters between the inner and outer quantifiers before it can conclude that — roughly 2³⁰ attempts. Add one more a and it doubles.

A JavaScript regex cannot be interrupted once it starts, so a pattern like that freezes the tab or, on a server, pins a CPU core and stops answering requests. That is ReDoS — a denial of service triggered by a single small input. It is a real and regularly exploited vulnerability class, most often in user-supplied search filters and in validation regexes for email addresses and URLs.

The defences available inside a browser page are limited, and this tool uses all of them: the test string is capped at 20,000 characters, the match count is capped, compilation and execution are wrapped in try/catch, and a run taking more than 250 ms is reported with a warning. It cannot prevent a pathological pattern from being slow — nothing in-page can — but it makes the problem visible before you deploy it.

The warning signs are nested quantifiers — (a+)+, (a*)*, (.*)* — and alternations where the branches can match the same text, like (a|a)*. If your pattern contains one and runs against input a user controls, rewrite it before it ships.

The fixes are structural. Make the inner and outer quantifiers unambiguous so there is only one way to match. Replace .* with a negated class that cannot cross the delimiter. Anchor the pattern so the engine cannot retry from every starting offset. And bound the input length before matching — most ReDoS is only reachable because a 100 KB string was allowed into a validator that expected 50 characters.

Command-line equivalents

# ERE — no \d, no lazy quantifiers, no lookaround
grep -E '[0-9]{1,3}(\.[0-9]{1,3}){3}' access.log

# PCRE via grep -P — closest to JavaScript; -o prints just the match
grep -Po '(?<=status=)\d+' app.log | sort | uniq -c

# ripgrep uses Rust's regex crate: no backtracking, so no ReDoS,
# but also no backreferences or lookaround
rg '\b(?:[0-9]{1,3}\.){3}[0-9]{1,3}\b' access.log

# sed with ERE, capturing and reordering
sed -E 's/^([^ ]+) ([^ ]+)/\2 \1/' file.txt

# Test a JavaScript regex exactly as this page does, from a shell
node -e 'const re=/\b\d{1,3}(?:\.\d{1,3}){3}\b/g;
  console.log("host 10.0.0.1 and 192.168.1.7".match(re))'
# -> [ '10.0.0.1', '192.168.1.7' ]

# Extract a DKIM selector from a mail header
grep -Eo 's=[^;]+' headers.txt
The same patterns outside the browser

That last example is a good reminder of scope. Regex is excellent at pulling a token out of a line of text and poor at parsing structured formats. Do not parse JSON with it — use the JSON formatter or jq. Do not validate email addresses with it either; the RFC 5322 grammar is genuinely not regular, every "complete" email regex you find is wrong somewhere, and the only reliable test is to send a message and see whether it is accepted. Check for an @, check the domain has an MX record, and let the mail server be the judge.

JSON Formatter
Pretty-print, minify, and validate JSON

Frequently asked questions

Which regex flavour does this tester use?
JavaScript — it compiles your pattern with the browser's own RegExp, so results match exactly what your JS, TS, or Node code will do. Patterns using PCRE-only constructs such as atomic groups or possessive quantifiers will not compile here, because JavaScript does not have them.
Why does my pattern only find the first match?
The g flag is off. Without it, exec and String.match return only the first match — which is exactly how your code will behave too, so the tool deliberately reproduces it rather than quietly finding them all.
What is catastrophic backtracking?
A pattern whose matching time grows exponentially with input length, caused by nested quantifiers such as (a+)+ where the engine must try every possible split before failing. On a server it pins a CPU and stops the process answering requests — a denial of service from one small input.
Why is the test string limited to 20,000 characters?
Because a JavaScript regex cannot be aborted once it starts. On a longer subject a pathological pattern would freeze your browser tab with no way to recover. The cap plus the timing warning is the only protection available inside a page.
How do I match across multiple lines?
Use the s (dotAll) flag so . matches newlines. The m flag is a different thing — it makes ^ and $ match at line boundaries. People reach for m when they need s more often than the reverse.
What is the difference between (…) and (?:…)?
(…) captures the matched text and makes it available as $1, $2, and so on. (?:…) groups without capturing. Use non-capturing groups whenever you only need grouping — adding a capturing group renumbers every group after it, which silently breaks code that reads them positionally.
Should I use a regex to validate email addresses?
No. The RFC 5322 address grammar is not regular, and every "complete" email regex in circulation rejects valid addresses or accepts invalid ones. Check for a single @, confirm the domain publishes an MX record, and send a confirmation message — that is the only test that reflects reality.
Is my pattern or test data uploaded anywhere?
No. Compilation and matching run entirely in your browser using the built-in regex engine. The page makes no network request, which matters when the text you are testing against is a production log excerpt.