Regular expressions pack a lot into one line, which is exactly why they are hard to verify by reading. Whether \d{3}-\d{4} catches everything you intended — and nothing you did not — only becomes clear when you run it against real data.
This tester highlights matches as you type, breaks out capture groups and named groups in a table, and previews replacement output before you commit to it. Common patterns for email, URL, IPv4, phone numbers and UUIDs are one click away, and a cheat sheet stays open below the workspace.
How to use
- Write the pattern and set flags — Enter the expression between the slashes and toggle flags:
gfinds every match,iignores case,mmakes^and$match at line boundaries. Syntax errors are reported directly under the field. - Provide realistic test input — Include both strings that should match and strings that should not. Most regex bugs are not about missing matches — they are about matching too much.
- Verify with highlight and list views — The highlight view shows matched spans in context; the list view gives each match's index and captured groups. A group showing
undefinedmeans it was optional or sat in an untaken alternation branch. - Preview the replacement — In the replace view, use
$&for the whole match,$1for the first capture group and$<name>for a named group, and see the result immediately.
Frequently asked questions
Will patterns built here work in Python or Java?
This tool uses the JavaScript (ECMAScript) engine, and the core syntax is shared across languages.
Details differ, though. JavaScript has no \A or \z absolute anchors — use ^ and $. \d matches ASCII digits only in JavaScript, while Python includes Unicode digits by default. Named groups use (?<name>...) in JavaScript, Python and Java alike, but Python's replacement reference is \g<name> rather than $<name>.
Only the first match shows up.
Check whether the g (global) flag is on.
Without g, exec and match return the first match and stop, and this tool follows the same rule. Note the mirror-image trap in code: calling test() repeatedly with a g regex keeps lastIndex between calls, so results alternate between true and false. For a simple existence check, leave g off.
My regex suddenly hangs on long input.
That is almost certainly catastrophic backtracking.
Nested quantifiers such as (a+)+b make the number of paths the engine must try grow exponentially with input length. In a running service this is the ReDoS denial-of-service class. Avoid nested quantifiers, narrow .* to something like [^"]*, and never interpolate untrusted user input into a pattern.
Concepts worth knowing
Greedy versus lazy quantifiers
*, + and {n,m} are greedy by default: they consume as much as possible, then give characters back only as needed. That is why <.*> applied to <b>bold</b> matches the entire string rather than just <b>.
Adding ? makes a quantifier lazy, consuming as little as possible, so <.*?> matches <b> and </b> separately. For HTML tags, quoted strings and bracketed log segments, knowing this one distinction resolves most problems.
Capturing, non-capturing and named groups
(...) stores what it matched. If you only need grouping, (?:...) avoids the storage — unnecessary captures clutter results and cost a little performance.
Once you have several groups, counting $1 and $2 becomes miserable. Naming them — (?<year>\d{4})-(?<month>\d{2}) — lets your code read match.groups.year and keeps working when you insert another group later.
Lookahead and lookbehind
(?=...) asserts that something follows without consuming it; (?!...) is its negation. (?<=...) and (?<!...) look backwards.
They shine when several conditions must hold at once. ^(?=.*[a-z])(?=.*[A-Z])(?=.*\d).{8,}$ expresses 'at least eight characters including a lowercase letter, an uppercase letter and a digit' in a single line, because each lookahead checks from the same position without consuming input.