I lost most of an afternoon once to a single greedy quantifier. I was writing a redirect map for a WordPress migration β a few hundred old URLs that needed rewriting to new slugs β and I had a regex that was supposed to capture the trailing slug from each path. It worked on the three URLs I tested in my head. It did not work on the fourth, which had two path segments, because .* had cheerfully swallowed the first slash and everything after it. The rule silently redirected half the site to the wrong place, and I found out from a reader, not from my testing, because my "testing" was staring at the pattern and trusting it.
That is the core problem with regular expressions: they are write-only until you run them against something. A pattern reads as plausible right up to the moment real input proves it wrong, and the failure modes are quiet. A regex that matches nothing throws no error. A regex that matches too much throws no error. A regex with a subtly wrong capture group hands you the wrong substring and moves on. The only way to know a pattern does what you think is to feed it the messy, edge-case-laden text it will actually see and look at what it grabs.
A regex tester closes that gap. You put your pattern in one box, your sample text in another, and every match lights up in place β with the capture groups broken out so you can see exactly which slice of the string landed in group 1 versus group 2. The one I built for toolz.dev runs on the browser's native RegExp engine, the same one your JavaScript and TypeScript code uses, so what you see in the tester is what you get in production. No approximation, no "close enough."
This guide covers how to use the tester to debug patterns fast, what each flag actually does, how capture groups and named groups show up in the output, and the JavaScript-specific gotchas that trip people coming from Python or PCRE.
TL;DR: Paste your pattern into the toolz.dev Regex Tester (no slashes needed), toggle the g/i/m/s/u/y flags, and drop your test text below. Matches highlight live with alternating colors, every numbered and named capture group is listed per match, and syntax errors show the engine's exact message. It uses the real JavaScript RegExp engine, runs 100% client-side so sensitive logs never upload, and pairs with the Regex Builder for assembling patterns from scratch.
How Do You Test a Regex Without Running Your Whole Program?
The slow way to debug a regex is the loop most of us start with: edit the pattern in your code, run the program, read the output, guess what went wrong, repeat. Every iteration costs a full program run, and the feedback is indirect β you see the consequence of the match, not the match itself.
A tester collapses that loop to nothing. You paste the pattern, paste representative text, and see the matches immediately. When you change one character in the pattern, the highlights update. This tightens the feedback to the point where you can explore β try a + where you had a *, add a word boundary, make a group non-capturing β and watch the effect land in real time instead of imagining it.
The key discipline is using real test text, not the clean example in your head. The URL that broke my redirect map had two path segments; my mental test case had one. If I had pasted the actual list of old URLs into a tester, the greedy match would have been visibly wrong on line four. Grab the actual log lines, the actual user inputs, the actual file paths β including the ugly ones β and let the tester show you where the pattern falls apart before your users do.
What Do the Regex Flags Actually Change?
Flags modify how the whole pattern is applied, and misunderstanding them is behind a large share of "why doesn't this work" moments. The tester exposes all six JavaScript flags as toggles so you can flip one and watch the result shift.
g β global. Without it, matching stops at the first hit. With it, the engine finds every match in the text. In the tester, global is on by default because you almost always want to see all matches; turn it off to confirm what a single-match call like String.match without g would return.
i β ignore case. Makes the whole pattern case-insensitive, so error matches Error and ERROR. Straightforward, and the most-used flag after g.
m β multiline. This one is widely misunderstood. It does not make . cross line breaks. It changes what ^ and $ anchor to: with m, they match at the start and end of each line, not just the start and end of the whole string. If you're matching line-by-line in a log, you want this.
s β dotAll. This is the flag that makes . match newline characters. Without it, . matches any character except a line break, which is why a pattern meant to span multiple lines often quietly stops at the first one. If your match should cross line boundaries, s is what you need β not m.
u β unicode. Enables full Unicode mode, which makes \u{...} escapes work, treats surrogate pairs as single code points, and makes character classes like \p{Letter} available. Important when your text has emoji or non-Latin scripts.
y β sticky. Anchors each match attempt to the exact position of lastIndex, so matching only succeeds if it starts precisely there. It's a niche flag used in tokenizers and parsers; most everyday work never touches it.
The confusion that costs the most time is m versus s. People reach for multiline when they mean dotAll. If your dot isn't crossing lines, you need s. If your anchors aren't hitting line boundaries, you need m. They solve different problems and are often used together.
How Are Capture Groups Shown in the Output?
Parentheses in a pattern do two jobs: they group sub-patterns for quantifiers or alternation, and they capture the matched slice for extraction. When you're using a regex to pull data out of text β a date's year and month, a log line's timestamp and level β the capture groups are the entire point, and getting them wrong is the most common source of "the pattern matches but I got the wrong string."
The tester lists every match with its capture groups broken out beneath it. Group 1 is the first parenthesized sub-pattern, group 2 the second, and so on, each shown with the exact text it captured for that match. This makes group-boundary bugs obvious: if group 1 grabbed more than you expected, you can see it and pull the parenthesis in.
Named groups get the same treatment. When you write (?<year>\d{4})-(?<month>\d{2}), the tester shows the captures by name β <year> and <month> β alongside their numbered equivalents, so you can confirm the names map to the slices you intend before you rely on match.groups.year in code. Named groups make patterns self-documenting and code more readable, and seeing them resolve correctly in the tester is the fastest way to trust them.
A related trap the tester surfaces: a group that participates in the pattern but didn't match on a given attempt shows as "no match" rather than an empty string. That distinction matters, because in JavaScript such a group is undefined, and code that assumes it's always a string will throw. Seeing it flagged in the tester tells you to guard that access.
Which Regex Flavor Is This, and Why Does It Matter?
Regular expressions are not one language. The pattern that works in Python's re module can behave differently β or throw β in JavaScript, and vice versa. This tester uses the JavaScript (ECMAScript) engine built into your browser, which is byte-for-byte the same engine Node.js runs. So if you're writing JavaScript, TypeScript, or Node code, the results here are exactly what your code will do.
That precision cuts both ways. If you copy a pattern from a Stack Overflow answer written for PCRE (PHP, and the preg_ functions I use constantly in WordPress work) or for Python, some features won't translate. JavaScript historically lacked lookbehind assertions, gained them relatively recently, and still handles some Unicode property escapes differently. Possessive quantifiers and atomic groups from PCRE don't exist in JavaScript at all. Recursion β a PCRE feature for matching nested structures β has no JavaScript equivalent.
The practical upshot: test in the flavor you'll deploy in. A tester that faithfully implements one engine is more useful than one that implements a fuzzy average of all of them, because "it worked in the tester" needs to mean "it will work in my code." For PHP and WordPress work I keep the differences in mind and verify server-side separately; for anything JavaScript, this tester is the source of truth. The broader toolkit for this kind of language-aware verification is covered in the coding tools guide.
What Are the Everyday Uses for a Regex Tester?
Building and Debugging Validation Patterns
Email fields, phone numbers, postal codes, slugs, semantic version strings β every form validates something, and the validation is usually a regex. The tester lets you throw the awkward cases at your pattern before it ships: the email with a plus-addressing tag, the phone number with an extension, the version string like 1.10.0 that a naive pattern truncates. Paste a column of real values and watch which ones fail to match.
Extracting Data from Logs
Server logs, application logs, and CSV exports are semi-structured text, and regex is how you pull fields out of them. Capture groups grab the timestamp, the level, the request ID. The tester's per-match group breakdown is exactly the view you need here β paste a dozen real log lines, and confirm every field lands in the right group across all of them, not just the tidy first line. This dovetails with the API debugging workflow where response bodies and headers get scraped for specific values.
Search-and-Replace Across a Codebase
Editors and IDEs support regex in find-and-replace, and a bad pattern there can rewrite far more than intended. Testing the match side in a dedicated tester before you run the replace is cheap insurance β see exactly what the pattern selects across a representative sample, then run the replace with confidence. My redirect-map disaster was fundamentally a find-and-replace that I never tested against real input first.
Learning and Teaching Regex
The live highlighting makes the tester a genuine learning tool. Build a pattern one token at a time and watch the match set shrink and grow as you add anchors, quantifiers, and classes. Seeing \d+ grab a whole number and then \d grab a single digit does more for understanding than any prose explanation. When you're ready to assemble a pattern from components rather than debug an existing one, the Regex Builder is the companion tool.
How Do You Avoid the Zero-Length Match Trap?
A pattern that can match an empty string β a*, \d*, (foo)? β can "match" at every position between characters, producing a flood of empty matches. Naive matching code that doesn't advance past a zero-length match loops forever. The tester guards against this internally by always advancing, so it never hangs, but it will show you the empty matches.
That display is a feature, not noise. A pile of empty matches is the tester telling you your quantifier is too permissive. If you meant "one or more digits," you wrote \d* when you wanted \d+. If a group should be required, you marked it optional with ?. Seeing the empty matches is the diagnostic that points you at the fix. When your pattern is right, the matches are the substrings you care about and nothing else.
JavaScript Regex vs Other Flavors: A Quick Comparison
| Feature | JavaScript (this tester) | PCRE (PHP) | Python re |
|---|---|---|---|
| Named groups | (?<name>...) |
(?<name>...) or (?P<name>...) |
(?P<name>...) |
| Lookbehind | Supported (modern engines) | Supported | Supported |
| Atomic groups / possessive | Not supported | Supported | Limited |
| Recursion | Not supported | Supported | Not supported |
| Dot matches newline flag | s (dotAll) |
s (DOTALL) |
re.DOTALL |
Unicode property \p{...} |
Requires u flag |
Requires u modifier |
regex module only |
| Sticky matching | y flag |
\G anchor |
\G-like via match position |
The table's lesson is that "regex" is a family, not a standard. The syntax overlaps enough to lull you into copying patterns across languages, and the differences are exactly the advanced features you reach for on hard problems. Test in your target flavor. For JavaScript and Node, that's this tester; the web developer toolkit covers where the other language-specific tools fit.
Is It Safe to Test Regex Against Sensitive Text?
The text you test regex against is often the sensitive kind: production log lines with IP addresses and user IDs, exports with email addresses, config files with internal hostnames. That's precisely the data you shouldn't paste into a tool that ships it to a server.
The toolz.dev Regex Tester runs entirely in your browser. The pattern compilation, the matching, the group extraction β all of it is client-side JavaScript, with no network request carrying your pattern or your text anywhere. Cut your connection after the page loads and it keeps working. That's an architecture guarantee, not a privacy-policy sentence, and it's the same browser-first principle behind every tool in the set, laid out in the data privacy guide.
Which means you can paste the real log lines β the ones with the actual PII that makes a pattern's edge cases show up β and debug against them safely. That's the whole point: the tester is only useful if you feed it real input, and real input is often exactly what you can't send to someone else's server.
FAQ
How do I test a regular expression online?
Enter your pattern in the pattern field without the surrounding slashes, choose your flags, and paste sample text into the test area. Matches highlight instantly and capture groups are listed for each match. Everything runs in your browser β nothing is uploaded.
What regex flavor does this tester use?
It uses the JavaScript (ECMAScript) RegExp engine built into your browser, which is identical to the one in Node.js. Results match how the same pattern behaves in JavaScript and TypeScript. JavaScript regex differs from PCRE, Python's re module, and Java patterns in some advanced features like atomic groups and recursion, so test in the flavor you will deploy in.
What do the regex flags g, i, m, and s mean?
The g (global) flag finds all matches instead of stopping at the first. The i flag makes matching case-insensitive. The m (multiline) flag makes the ^ and $ anchors match at line breaks rather than only the string start and end. The s (dotAll) flag lets the dot match newline characters too. The m and s flags are often confused β m changes the anchors, s changes the dot.
How do capture groups work in the tester?
Parentheses in your pattern create capture groups. The tester lists each numbered group with the text it captured for every match, so you can verify which slice of the string each part of the pattern grabbed. Named groups written as (?
Why does my regex match nothing or throw an error?
A pattern that matches nothing usually has an over-specific character or an unescaped special character like a literal dot or plus sign. A pattern that throws an error has a syntax problem, such as an unbalanced bracket or parenthesis. The tester displays the engine's exact error message so you can locate the issue quickly.
How do I match across multiple lines?
Enable the s (dotAll) flag if you need the dot to span line breaks, and enable the m (multiline) flag if you want the ^ and $ anchors to match at the start and end of each line. Combining both lets a single pattern work naturally against multi-line input like logs or CSV rows.
Is it safe to test regex against sensitive data?
Yes. All matching happens in JavaScript inside your browser β no pattern or test text is sent to any server, nothing is logged, and the tool works with your connection disabled. You can safely test against log files, personal data, or proprietary formats.
What is the difference between a regex tester and a regex builder?
A regex tester runs an existing pattern against text so you can verify and debug it, while a regex builder helps you construct a pattern from components or common templates. Use the Regex Builder on this site to assemble a pattern, then bring it to the tester to check it against real sample data.
Regular expressions stay write-only until you run them against real input, and the failures are quiet β nothing matches, too much matches, the wrong group captures, and no error tells you. A tester turns the pattern visible: paste your regex, feed it the ugly real text, and watch exactly what it grabs before it grabs the wrong thing in production.

