Regex Tester: Test, Explain and Debug Regular Expressions in the Browser
A regular expression that works on your first try is rare. Most patterns are written in a rush, tested against one string that happened to be at hand, and then quietly break two weeks later on a different input. A regex tester exists to close that gap: it shows what the engine actually matched, where the match starts and ends, what each group captured, and what happens when the flags change. The tool on this page runs entirely in your browser and covers both the JavaScript engine and PCRE2, so you can move a pattern between the two without guessing.
Why a regex tester belongs in your daily loop
Regular expressions sit in a strange place in a developer's toolkit. They are compact enough to write inline, powerful enough to replace a small parser, and unreadable enough that even the author struggles a day later. The gap between "this pattern looks right" and "this pattern is right" is where most bugs hide.
A tester closes that gap by turning a pattern into visible output. You paste the pattern, paste realistic input, and immediately see every match with its index and span. That last part matters more than it sounds. A pattern that matches the substring you wanted but starts three characters earlier will still return "matched" in a boolean test, and still write the wrong value into your database.
The second reason is speed. Rewriting a pattern, reloading the page, and rerunning a unit test is a slow loop, and most people stop iterating after two or three attempts. Typing directly into a tester and watching the highlight change with each keystroke keeps you in the loop long enough to get the edge cases right. The third reason is confidence: a tester shows the failure, not just the absence of success, so an invalid pattern surfaces as an error message instead of a silent no-match.
Reading a match list like a debugger
The match list is the part of a tester people underuse. A count alone tells you whether something matched. The list tells you why.
Each row carries the match index, the character span, the captured text for every numbered group, and the value of each named group. That structure answers the questions that actually come up during debugging: did the optional group capture an empty string, or did it not participate at all? Did the greedy quantifier swallow the delimiter that the next part of the pattern needed? Is group three the month, or did the alternation shift the numbering because one branch has an extra pair of parentheses inside it?
Capture group numbering is a common source of production bugs, because inserting a non-capturing parenthesis is easy to do and easy to forget. A tester that shows group values side by side with the span makes the shift obvious in a second. The same row list works as documentation: copy three representative rows into a ticket and the reviewer sees exactly what the pattern was supposed to do.
JavaScript flags you actually need
Flags change the meaning of a pattern more than most people expect, and the tool exposes all seven that JavaScript defines today.
| Flag | Name | What changes |
|---|---|---|
| g | global | finds every match instead of stopping at the first |
| i | ignore case | case-insensitive comparison |
| m | multiline | ^ and $ match at line boundaries as well as at string ends
|
| s | dotAll | . also matches newline characters
|
| u | unicode | code point semantics, required for \p{...} property escapes
|
| y | sticky | matching starts exactly at lastIndex, no scanning forward |
| d | indices | match result carries start and end indices for groups |
The two flags that cause the most confusion in practice are g and y. A global pattern keeps state between calls through lastIndex, so reusing the same object in a loop can skip matches; a sticky pattern refuses to search, which is what you want when parsing a token stream position by position. Toggling each flag inside a tester and watching the highlight move is faster than reading about the difference.
The u flag deserves a separate mention. Without it, a pattern like . counts UTF-16 code units, so an emoji is two matches. With it, the same pattern counts code points, and property escapes such as \p{Script=Greek} become available. If a validator behaves correctly in a tester and incorrectly in production, the first thing to compare is the flag set, not the pattern.
PCRE2 versus JavaScript: same pattern, different engine
The two engines share the core syntax and diverge in the corners, which is exactly where migrations break. PCRE2 supports possessive quantifiers, atomic groups, recursion, conditional patterns and \K, and it allows variable-length lookbehind in recent versions. JavaScript does not offer possessive quantifiers or atomic groups, keeps lookbehind fixed-length in most engines, and rejects recursion outright.
This matters when the same rule is enforced twice: on the client with JavaScript and on the server with PHP, Python or nginx, all of which lean on a PCRE-style engine. Testing only one side gives a false sense of safety, because a pattern that passes in the browser can throw a compile error on the server, and the reverse also happens.
A tester that switches engines on the same pattern and the same text turns that comparison into one click. PCRE2 here is compiled to WebAssembly and loaded only when you select it, so the page stays light until you need the second engine.
Catastrophic backtracking, caught before production
Backtracking engines try alternatives until something works. When a pattern contains nested quantifiers over overlapping character sets, the number of alternatives can grow exponentially with input length. The classic example is ^([a-z]+)*$ tested against a long string that ends with a character outside the class: with 28 characters the engine already spends seconds exploring paths that cannot succeed.
The practical risk is not a slow page. It is a denial-of-service vector: one crafted input on a public endpoint can pin a CPU core, and every request behind it waits.
Not every nested quantifier is dangerous, though. A pattern like [a-z0-9]+(?:-[a-z0-9]+)* is flagged by structural heuristics, yet each repetition must begin with a literal hyphen, which makes the split unambiguous and keeps matching linear. That is why a warning is a reason to measure, not a verdict: the tester reports the structural risk, and a timing test on a long string settles the question. For genuinely ambiguous shapes, the fix is usually to remove the nesting, anchor on a delimiter, or rewrite the construct as a single character class.
Presets that save the typing
Most patterns people need already exist. The tester ships 55 presets in six categories: contacts and network, identifiers and codes, dates and time, finance and documents, text and numbers, web and markup. A search box filters them by name or by pattern text, and the list is alphabetical inside each group.
| Intent | Pattern | Notes |
|---|---|---|
| Email, pragmatic | [a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}
|
fast, accepts most real addresses |
| Email, strict | preset "Email (strict)" | rejects consecutive dots and bad TLDs |
| URL | preset "URL (full)" | requires scheme and host |
| Date, ISO | \d{4}-\d{2}-\d{2}
|
add validation for real calendar days |
| IBAN | preset "IBAN" | country prefix plus length check |
| Slug | preset "Slug" | lowercase words joined by hyphens |
Presets are a starting point, not a final answer. Load one, look at the match list against your own data, then tighten the parts that produce false positives. Comparing a pragmatic email pattern with a strict one on the same input is a good exercise: the strict version fails on addresses that exist in the wild, and the pragmatic one accepts a few that do not.
Explaining and drawing a pattern
Two features help when a pattern arrives from someone else's code. The explain view walks the pattern node by node and prints each construct with what it means: anchors, character classes, quantifiers with their bounds, groups, alternations, lookarounds, backreferences. Reading a list of nodes is faster than parsing a wall of punctuation, especially for patterns that mix named groups with inline modifiers.
The diagram view draws the same structure as a railroad graph: a horizontal path with branches for alternation and loops for repetition. Diagrams are useful in code review, because reviewers recognise a loop and a branch faster than they decode (?:...). Both views stay in the browser, and the diagram can be downloaded as SVG or PNG when you need to paste it into documentation.
Exporting matches and sharing a session
When the pattern behaves, the next step is using it. Export writes the current match set as JSON, JSON Lines, CSV or plain text, which covers the common cases: JSON for a fixture file, JSON Lines for a log, CSV for a spreadsheet handed to someone in support, plain text for a quick paste into a ticket.
The share link encodes the pattern, the flags, the test text, the replacement and the engine into the URL. That turns a bug report into a reproducible case: send the link and the other person sees the exact state, including the failing input. Without it, bug reports about validation usually degrade into screenshots and guesses.
The replacement field previews the result with $1 and $<name> references before you commit anything. A substitution that looks right in the source often produces doubled text or a dropped separator, and seeing the output next to the input catches that immediately.
A five-minute workflow
Start from real input, not from an invented example. Copy three strings that must match and two that must not, then paste them into the test area.
Write the pattern in the smallest form that matches the positive cases, and check that the negative cases still fail. Then look at the match list to confirm which part of the text was captured; a pattern can be technically correct and still capture the wrong span.
Switch the engine if your production code is not JavaScript. Test with the flags your code actually uses, and test once with the flags you assumed. Run the pattern against a long input to check that matching time stays flat.
Finally, share the link with whoever reviews the change, or export the matches as a fixture so the same cases are checked automatically later. That last step is what turns a one-off test into a regression test.
Frequently Asked Questions
Is this regex tester free?
Yes. The tool runs in your browser with no account, no usage limit and no upload of your pattern, text or files. PCRE2 support downloads a WebAssembly module on first use and stays cached afterwards.
Does it send my test data anywhere?
No. Matching happens locally in the page, and the results never leave the browser. The only network request is the PCRE2 module, which is fetched from the same site when you switch engines.
What is the difference between JavaScript and PCRE2 here?
JavaScript uses the browser's built-in engine with the seven standard flags. PCRE2 is the engine behind PHP, Python and nginx, and it adds possessive quantifiers, atomic groups, recursion and \K, which JavaScript does not support.
Why does my pattern work in the tester but fail on the server?
Check the flag set and the engine first. Case sensitivity, multiline anchors, Unicode mode and sticky matching all change the result, and a pattern written for PCRE2 can fail to compile in JavaScript or the other way around.
What is catastrophic backtracking?
It is exponential work inside a backtracking engine, caused by nested quantifiers over overlapping sets, as in ^([a-z]+)*$. Long inputs can take seconds or minutes, which turns a validation rule into a denial-of-service risk.
Does the ReDoS warning mean my pattern is broken?
Not necessarily. The warning is structural, so it also fires on safe shapes such as [a-z0-9]+(?:-[a-z0-9]+)*, where every repetition starts with a fixed character. Measure timing on a long string before rewriting anything.
Can I generate a pattern from sample strings?
Yes. The "Pattern from examples" action derives a pattern from the strings you provide, which is a fast way to get a first draft. Treat the result as a draft: review the match list and tighten the character classes.
Can I save or share my test case?
The share link stores the whole session in the URL, including the pattern, flags, test text, replacement and engine. Export additionally writes the current matches as JSON, JSON Lines, CSV or plain text.
Which export formats are available?
Four: JSON, JSON Lines, CSV and plain text. JSON suits fixtures and API tests, JSON Lines suits logs, CSV suits spreadsheets, and plain text suits bug reports and chat messages.
Ko Tao, Thailand · SO reputation 2000 · Badges: 1🥇18🥈16🥉 · SO member since 2012