Regex Tester & Debugger
Test a JavaScript regular expression with live highlighting, per-match positions, capture group values, flag toggles and a replacement preview. All processing happens locally in your browser.
Test a pattern, inspect matches and capture groups
Matching uses the browser's own RegExp, so results are exactly what your JavaScript will produce. Every match is listed with its position and group values, and highlights are rendered from index ranges rather than injected HTML.
| # | Match | Position | Groups |
|---|---|---|---|
| 1 | 2026-03-14 | 9–19 · line 1, col 10 | $1 = 2026 · $2 = 03 · $3 = 14 |
| 2 | 2026-04-02 | 29–39 · line 1, col 30 | $1 = 2026 · $2 = 04 · $3 = 02 |
| 3 | 2026-11-30 | 53–63 · line 1, col 54 | $1 = 2026 · $2 = 11 · $3 = 30 |
Global flag is on, so every match is found.
What is a regular expression, and how does matching work?
A regular expression is a small pattern language for describing text. The engine reads your pattern left to right, and at each position in the subject string it tries to satisfy the next piece of the pattern.
Two behaviours explain almost everything a regex does:
- It scans forward. Matching is attempted at position 0. If that fails, position 1, then 2, and so on. This is why
catfinds "cat" anywhere in "the cat sat". - Quantifiers are greedy, then backtrack.
.*first consumes everything to the end of the line, then gives characters back one at a time until the rest of the pattern can match.
That second point is the source of most surprising results. Against <a><b>:
<.*> matches <a><b> ← greedy: took everything, backtracked to the last >
<.*?> matches <a> ← lazy: took as little as possible
<[^>]*> matches <a> ← explicit: can never cross a > at all
The third form is usually the best of the three. It states the intent directly instead of relying on backtracking, and it is faster.
Engine: this page uses the browser's built-in JavaScript RegExp, so what you see here is exactly what your JavaScript, TypeScript or Node code will do. Constructs from PCRE and .NET — \A, \z, atomic groups (?>...), possessive quantifiers a++, recursion, \K, inline (?i) modifiers — do not exist in JavaScript and are flagged rather than silently mishandled.
Flags change the rules, not the pattern
| Flag | Name | Effect | When you need it |
|---|---|---|---|
g |
global | Find every match instead of stopping at the first | Counting occurrences, replacing all instances |
i |
ignoreCase | Letter case is ignored | Matching user input, header names |
m |
multiline | ^ and $ match at each line break |
Processing line-oriented text such as logs or config |
s |
dotAll | . also matches newline |
Matching across lines, e.g. an HTML block |
u |
unicode | Pattern works on code points; enables \p{...} |
Emoji, accented text, script-based classes |
y |
sticky | Match only at lastIndex, no scanning |
Writing a tokeniser or parser |
d |
hasIndices | Records start and end positions per group | Syntax highlighting, editor tooling |
The m flag is the one worth understanding properly. Without it, ^ means "start of the whole string". With it, ^ means "start of any line". Against a three-line log file, ^ERROR without m can match at most once; with m it can match on every line.
The u flag matters more than it looks. . without it matches one UTF-16 code unit, so /^.$/ fails against a single emoji — the emoji is two units. With u, it succeeds.
Capture groups: numbered and named
Parentheses do two jobs at once: they group for quantifiers, and they capture the matched text for later use.
Pattern: (\d{4})-(\d{2})-(\d{2})
Subject: 2026-03-14
Full match → 2026-03-14
$1 → 2026
$2 → 03
$3 → 14
Named groups make longer patterns readable and survive reordering:
Pattern: (?<year>\d{4})-(?<month>\d{2})-(?<day>\d{2})
$<year> → 2026
$<month> → 03
$<day> → 14
Three group variants are worth keeping straight, because they look similar and behave very differently:
| Syntax | Captures? | Consumes text? | Purpose |
|---|---|---|---|
(abc) |
Yes | Yes | Group and capture |
(?:abc) |
No | Yes | Group without adding a capture slot |
(?=abc) |
No | No | Lookahead: require what follows, without including it |
(?!abc) |
No | No | Negative lookahead: require that it does not follow |
(?<=abc) |
No | No | Lookbehind: require what precedes |
"Consumes text" is the key column. Lookarounds check a condition at the current position without moving forward, which is why (?=.*\d)(?=.*[a-z]).{8,} can test several password rules against the same characters.
A group that did not participate in the match reports undefined, not an empty string. With (a)?(b) against "b", $1 is undefined. The match table on this page shows that distinction explicitly, because in code the two behave differently.
Escaping: which characters are special
Twelve characters have meaning outside a character class and need a backslash to be matched literally:
. ^ $ * + ? ( ) [ ] { } | \
So to match a literal price you need:
Wrong: $19.99 → $ is an anchor, . is any character
Right: \$19\.99 → matches only "$19.99"
Inside a character class the rules are different and smaller. Only ^ (when first), ], \ and - (when between two characters) are special:
[.+*] matches a literal dot, plus or asterisk — no escaping needed
[a-z-] a trailing hyphen is literal
[\]\\] a literal ] and a literal backslash
When a pattern comes from user input, escape it rather than trusting it. The String Escaper has a regex target that escapes every metacharacter for you.
Remember there are two layers when a pattern lives inside a string literal. To match one backslash the regex is \\, and writing that regex in a JavaScript string needs "\\\\". This page takes the raw pattern, so you type \\ — the same as inside /.../ literal notation.
Examples: how to solve common matching tasks
| Goal | Pattern | Flags | Note |
|---|---|---|---|
| Reformat a date | (\d{4})-(\d{2})-(\d{2}) → $3/$2/$1 |
g |
2026-03-14 becomes 14/03/2026 |
| Strip comment lines | ^\s*#.*$ |
gm |
Needs m so ^ matches per line |
| Find repeated words | \b(\w+)\s+\1\b |
gi |
\1 is a backreference to group 1 |
| Trim trailing whitespace | [ \t]+$ → empty |
gm |
Avoids \s so newlines survive |
| Split on commas outside quotes | ,(?=(?:[^"]*"[^"]*")*[^"]*$) |
g |
Works for simple cases; use a real CSV parser for data |
| Match any letter, any script | \p{L}+ |
gu |
\p{...} requires the u flag |
Common regex mistakes
- Forgetting
gand wondering where the other matches went. Without the global flag only the first match is returned. The tool states which mode you are in below the results. - Using
^and$withoutmon multi-line text. They anchor to the whole string, so a pattern that works on one line fails on a file. - Unescaped dots.
example.comalso matches "exampleXcom". Writeexample\.com. - Greedy
.*swallowing too much. Prefer a negated class such as[^>]*over.*?where possible; it is clearer and faster. - Nested quantifiers causing catastrophic backtracking.
(a+)+$against a long run of "a" followed by "b" can take exponential time. If a pattern has a quantifier inside a quantified group, restructure it. - Zero-length matches in a loop. A pattern like
a*matches emptily at every position. In a manualexecloop you must advancelastIndexyourself or it never ends. Zero-length matches are marked in the highlight panel here. - Reusing a global regex object. A
/gregex keepslastIndexbetween calls, sotest()on the same regex alternates true and false. Create it fresh, or resetlastIndex. - Trying to parse HTML. Nested, optional and self-closing tags are not a regular language. Use
DOMParserfor structure and regex only for narrow, well-defined fragments. - Copying a PCRE pattern from a PHP or Python answer.
\A,(?>...)and possessive quantifiers throw a syntax error in JavaScript. The tool names the specific construct when it spots one.
Use cases
- Validating a form field. Build the pattern here against real and deliberately malformed samples before shipping it.
- Extracting fields from log lines. Named groups turn an unstructured line into labelled values you can read straight off the match table.
- Bulk find and replace in an editor. VS Code, IntelliJ and Sublime all use JavaScript-compatible syntax with
$1substitution, so a pattern verified here transfers directly. - Writing a URL rewrite rule. Test the capture groups before putting them into an Nginx or CDN configuration.
- Auditing an inherited pattern. Paste a regex from a codebase and see exactly what it matches, and what it misses.
- Cleaning up scraped text. Iterate on a replacement against a sample until the output is right.
Privacy and limits
The pattern and test string stay in your browser; matching runs on the page's own JavaScript engine and nothing is uploaded.
Practical limits keep the page responsive: test strings are capped at 200,000 characters, highlighting at 500 matches, and the detail table lists the first 50. That is comfortably enough to verify a pattern, and it means a pathological expression cannot lock the tab.
Frequently Asked Questions
Which regex engine does this use?
The browser's native JavaScript RegExp. That is a deliberate choice rather than a limitation: it means a pattern that works here works identically in your JavaScript, TypeScript, Node and browser code, with no dialect translation.
Why does my PCRE pattern throw an error?
JavaScript does not implement several PCRE constructs. \A and \z, atomic groups (?>...), possessive quantifiers such as a++, recursion, \K and inline (?i) modifiers all fail. When one is detected the page names it and suggests the JavaScript equivalent where one exists.
What is the difference between (abc) and (?:abc)?
Both group for quantifiers, but only the first captures. Use (?:...) when you just need grouping — it keeps your group numbering stable, which matters when a replacement string refers to $1 and $2.
Why is a capture group undefined rather than empty?
Because the group did not participate in the match at all, which is different from matching zero characters. With (a)?(b) against "b", group 1 is undefined. In code that distinction matters: undefined fails a truthiness check while an empty string still needs a length test.
What is catastrophic backtracking?
A pattern where the engine has exponentially many ways to fail. (a+)+$ against 30 letter a's followed by a b can take longer than the age of the universe. The tell-tale sign is a quantifier applied to a group that already contains one. If a pattern feels slow, look there first.
Can I use this for a Java, Python or Go regex?
Mostly. The common core — character classes, quantifiers, anchors, groups, lookaheads — behaves the same across those engines. Differences appear in named group syntax, Unicode property support and lookbehind availability, so verify anything unusual in the target language.
Does the highlighting render my test string as HTML?
No. Matches are returned as index ranges and the highlights are built as React elements over plain text, so nothing in your input is ever interpreted as markup. That also means the positions shown are positions in your original string, not in an escaped copy of it.