About Regular Expression Tester
Regular Expression Tester compiles your pattern with the browser's own RegExp engine and reports, in one JSON object, how many matches it found, the text and offset of each, the positional and named capture groups, and the result of applying your replacement template to the whole text.
The engine here is ECMAScript, not PCRE. Lookbehind, named groups, and Unicode property escapes all work, but atomic groups, possessive quantifiers, recursion, inline modifiers such as (?i), the anchors \A and \z, and \K do not exist in JavaScript. A pattern lifted from a PHP, Python, or Perl codebase will often either fail to compile or, worse, compile into something that means something else. Decide up front which engine the pattern will finally run in.
How to use this developer tool
- Enter the pattern in the expression field without delimiters — no leading or trailing slash.
- Put the flags in the flags field as bare letters, such as gm or giu.
- Paste test text containing both the lines you expect to match and near-misses that must not.
- Run the test and read the report: count, then each match with its offset, positional groups, and named groups.
- Adjust the replacement template once the match boundaries are right; $1 is the first group, $<name> a named group, $& the whole match, and $$ a literal dollar sign.
Worked example: extracting service and severity from log lines
Three log lines, of which only two should match. Named groups keep the report readable, and the replacement template rewrites each matching line.
Pattern: (?<level>WARN|ERROR)\s+\[(?<svc>[\w-]+)\]\s+(?<msg>.+)
Flags: gm
Replacement: $<svc>/$<level>
Test text:
2026-03-04T10:15:02Z INFO [auth-api] token refreshed
2026-03-04T10:15:07Z WARN [auth-api] retry 1 of 3
2026-03-04T10:15:09Z ERROR [billing] upstream timeout after 30s{
"count": 2,
"matches": [
{
"match": "WARN [auth-api] retry 1 of 3",
"index": 75,
"groups": [
"WARN",
"auth-api",
"retry 1 of 3"
],
"namedGroups": {
"level": "WARN",
"svc": "auth-api",
"msg": "retry 1 of 3"
}
},
{
"match": "ERROR [billing] upstream timeout after 30s",
"index": 126,
"groups": [
"ERROR",
"billing",
"upstream timeout after 30s"
],
"namedGroups": {
"level": "ERROR",
"svc": "billing",
"msg": "upstream timeout after 30s"
}
}
],
"replaced": "2026-03-04T10:15:02Z INFO [auth-api] token refreshed\n2026-03-04T10:15:07Z auth-api/WARN\n2026-03-04T10:15:09Z billing/ERROR"
}The offsets 75 and 126 are positions in the test text, counted from zero with each newline taking one character. Notice that msg stops at the end of its line: there is no s flag, so .+ cannot cross a line terminator, which is what line-oriented input needs. The INFO line is untouched in the replaced output because it never matched.
Flags, character classes, and quantifiers
Every claim in the third column is specific to the JavaScript engine this tester uses; several differ in PCRE and in Python's re module.
| Token | What it does | Watch out for |
|---|---|---|
| g | Keeps scanning after the first match | The match list here is always global; the replacement is not, so without g only the first occurrence is rewritten |
| i | Case-insensitive matching | With u it applies Unicode case folding, so /k/iu matches the Kelvin sign U+212A while /k/i does not |
| m | ^ and $ also match at line boundaries | \r, \u2028, and \u2029 are line terminators too, so ^.*$ over CRLF text yields an extra empty match between \r and \n |
| s | Lets . match line terminators as well | Without it, /a.b/ does not match the two-line text a, newline, b |
| u | Unicode mode: \p{...}, \u{...}, code-point semantics | Without u, \p{L} is not an error — it silently matches the literal text p{L} |
| y | Anchors every attempt at lastIndex | With gy the scan stops at the first position that fails, so aa b aa yields only one match |
| . | Any character except a line terminator | Add s whenever a match is meant to span lines |
| \d and \w | ASCII digits, and [A-Za-z0-9_] | These stay ASCII even with u; use \p{Nd} and \p{L} for Unicode |
| \s | Whitespace | Unlike \d and \w it does cover Unicode spaces, including U+00A0 and the byte order mark |
| \b | A word boundary, defined in terms of \w | It therefore behaves unhelpfully around accented letters and CJK text |
| a* a+ a{2,} | Greedy: consume as much as possible, then give characters back | /<.+>/ over <a><b> matches the whole string, not the first tag |
| a*? a+? a{2,}? | Lazy: consume as little as possible, then take more | /<.+?>/g over <a><b> matches <a> and then <b> |
Troubleshooting
The tab freezes when you run the test.
Why it happens Catastrophic backtracking. A pattern such as (\s*\w+)+$ nests a quantifier inside a quantifier over overlapping character sets, so on input that almost matches the engine explores an exponential number of ways to split the text before concluding there is none.
Fix Never nest quantifiers over sets that can match the same characters. Replace .* between delimiters with a negated class such as [^,]*, and anchor the pattern so failure is detected early. If runtime jumps sharply as you add a few characters, the pattern is exponential.
A pattern with a dot stops at the end of a line.
Why it happens In JavaScript the dot excludes line terminators unless the s flag is set, and \r counts as a terminator just as \n does.
Fix Add s when one match should span lines. For line-oriented input leave s off deliberately — it is what stops a greedy .+ swallowing the rest of the file.
A Unicode property escape matches nothing, or the wrong thing, with no error.
Why it happens \p is only a property escape in Unicode mode. Without u it is an identity escape, so \p{L} compiles to the literal characters p{L} and matches that text instead.
Fix Add u whenever the pattern uses \p{...} or \u{...}, or needs astral characters such as emoji to count as one unit. Note that u also turns previously tolerated constructs into syntax errors, for example a lone { outside a quantifier.
An anchored pattern only matches the first line of the test text.
Why it happens Without the m flag, ^ and $ anchor to the start and end of the entire string rather than of each line, so only the first line can satisfy ^.
Fix Add m to make the anchors fire at line boundaries. Keep m and s straight: m changes where ^ and $ match, while s changes whether the dot can cross a line break.
The match list shows several matches but only the first was replaced.
Why it happens The tester forces the global flag when enumerating matches, so you always see all of them. The replacement uses your flags exactly as typed, and String.replace without g rewrites only the first occurrence.
Fix Put g in the flags field for a bulk replacement. This is worth knowing beyond the tool, because the same asymmetry bites application code that reuses one pattern for testing and for replacing.
When this tool is the right choice
- Proving that a log-parsing or validation pattern matches the lines you want and rejects the ones you do not.
- Checking where each capture group starts and ends before you index into groups in code.
- Previewing a bulk find-and-replace, including named-group templates, without touching the real file.
- Building a small reproducible case for a regex bug so it can go into a test suite beside the pattern.
When to use something else
- Parsing HTML or XML. Nested structures are not regular; use the HTML Previewer or the XML Validator instead.
- Validating email addresses against the full RFC 5322 grammar. Check for one at sign and a plausible domain, then send a confirmation message.
- Finding the differences between two blocks of text. The Text Compare tool does that properly.
Privacy and data handling
The pattern is compiled and run against your text inside this browser tab, and nothing is transmitted or stored. The realistic risk is the test data rather than the pattern: people paste real log excerpts when building a parser, and those lines carry IP addresses, session identifiers, and occasionally credentials. Trim the sample to the few lines that exercise the pattern.
Related developer tools
Frequently asked questions
Should I include the slashes around my pattern?
No. The expression field takes the pattern body only, and the flags have their own field. If you paste /^abc$/i the slashes become literal characters the pattern looks for, which is a common reason a pattern suddenly matches nothing.
Is this engine compatible with PCRE?
Partly, and the differences matter. Lookahead, lookbehind, named groups, and Unicode property escapes work. Atomic groups, possessive quantifiers, recursion, inline flag modifiers, \A, \z, and \K do not exist in JavaScript. Test in the engine that will run the pattern in production.
What is the difference between greedy and lazy quantifiers?
A greedy quantifier takes as much text as it can and then gives characters back until the rest of the pattern fits. A lazy one takes as little as possible and then extends. Over <a><b>, the pattern <.+> matches the whole string while <.+?> matches <a> and <b> separately.
How do I match a literal dollar sign in the replacement?
Write $$. In a replacement template the dollar sign introduces a substitution: $1 for the first group, $<name> for a named group, $& for the whole match. $$ escapes back to a single literal dollar.
Is lookbehind supported?
Yes. Lookbehind arrived in ES2018 and works in current Chrome, Firefox, and Safari 16.4 or later. JavaScript lookbehind is also variable-length, unlike several engines that require a fixed width, so a pattern legal here may be rejected elsewhere.
What does the v flag do?
Standardized in ES2024, v is a stricter superset of u that adds set operations inside character classes — intersection with &&, subtraction with --, and multi-character string properties. It is not interchangeable with u, since v also makes more characters require escaping inside a class.