Online Regex Tester: A Practical Guide to Debugging Regular Expressions

Online Regex Tester: A Practical Guide to Debugging Regular Expressions

Test a JavaScript regex, inspect capture groups, and preview a $1 replace in the browser before the pattern lands in production code.

26.09.2026
12 min read
Share this article:
Regex
Regular expressions
Validate
tools-dev
Tutorial

A pattern that looks right still deserves a failing input

A regular expression is a tiny program. It is easy to write, easy to paste from a comment thread, and expensive when it accepts the wrong string or hangs the tab. The online regex tester runs the browser's JavaScript RegExp engine on a pattern and a test string you paste locally — highlights on the left, match cards with groups on the right, and an optional replace preview. There is no flavor switch: it is not PCRE, not Python, not the engine inside a database. That is the same family as Node and as a JSON Schema pattern evaluated by Ajv. The page sits on the developer utilities hub, next to slugify and the case converter, for the jobs that should not be a hand-rolled expression at all.

JavaScript flags only: g, i, m, s, u, y. Global (g) is on by default and can be turned off
Match mode highlights hits and lists cards with numbered groups; a named group appears once as GROUP k (name)
Replace mode previews JavaScript substitution patterns such as $1, $&, and $<name>
Six built-in examples and a cheat sheet that inserts tokens at the caret. Sample loads the key=value example and runs it
Nothing is uploaded. Click Test to run — editing the pattern, flags, mode, or text clears the previous result
At most 500 matches are returned. A zero-width storm hits a loop guard. Nested quantifiers can still freeze the tab

Where the tester helps, and where a unit test should take over

Good fits for the page

A pattern you are about to paste into code, a fixture, or a JSON Schema pattern keyword.

Reproduce a bug with the exact subject from the ticket, including the line that should not match
Compare greedy and lazy on two quoted words before choosing a negated character class
Check that a JSON Schema pattern is JavaScript-valid before compiling the schema in Ajv
Preview a replace with $1 on a three-line sample, then copy the pattern into the code review
Load an example chip when you need a known-good starting point instead of a blank field
What does not belong in a regex playground

Some jobs already have a tool that encodes the rules. A hand-written expression will drift.

URL slugs: use slugify (separator, case, length) instead of a replace you will re-tune per locale
camelCase, snake_case, kebab-case: use the case converter. A regex for word boundaries breaks on digits and acronyms
Email, UUID, or date-time as a product rule: a schema format or a dedicated parser, not the email-ish chip
A pattern that must stay stable: commit it next to fixtures in the repo. The tester does not store your pattern in the share link — that control shares the tool page, not the current expression

Patterns that survive a glance and fail on real input

Debugging a PCRE snippet in a JavaScript box

The tester compiles the field with new RegExp. Recursive constructs such as (?R) are invalid here and surface the engine's error. The slashes you see around the field are chrome, not part of the pattern. Pasting /(\w+)/gi unwraps the pattern and merges those flags with the ones already toggled — it does not replace them. If g was already on, it stays on. A second trap is the backslash: the field is the regex source, so \d means digits. Pasting \\d from a JSON or Java string looks for a literal backslash followed by d.

Copying a PCRE-only pattern from a Stack Overflow answer and expecting the same matches
Typing the surrounding slashes into the field, so they become literal characters
Assuming a pasted /pattern/flags replaces the current flags instead of merging with them
Doubling every backslash because the pattern was copied from a string literal in source code
Letting a greedy quantifier swallow the rest of the line

On "one" and "two", the pattern ".+" with g returns one match: the whole span from the first quote to the last. The engine takes as much as it can, then gives characters back until the final quote fits. Switching to ".+?" is not “only the words you meant as a phrase”. With g it returns two matches, "one" and "two". Lazy means the shortest success, not a semantic filter. "[^"]*" is the pattern that cannot cross a quote.

Using .+ when the stop character is known, then wondering why one match ate the line
Treating a lazy quantifier as a semantic filter
Forgetting that g finds every success, including the second quoted word
Testing only the happy string that happens to contain one pair
Nested quantifiers on a long subject

A shape such as (a+)+ against a long run of a's that never reaches the expected ending can explode into catastrophic backtracking. The tester has no server timeout. It runs on the tab's main thread, so the page can freeze. The tool does cap what it returns: global search stops and shows a truncation warning, either because it hit the soft cap of 500 matches, or because a zero-width pattern tripped the loop guard (the engine advances one character after an empty match so g cannot spin forever). That guard does not make a nested quantifier safe. Upload accepts .txt, .md, and .log up to 32 MiB into the test string, still locally — a full log plus a nested pattern is a good way to lock the tab.

Dropping a production log into the subject while the pattern still has nested quantifiers
Reading a truncation warning as “the pattern is correct, there were just too many hits”
Testing \b on a long string and treating hundreds of empty cards as content matches
Assuming the browser will kill a runaway expression the way a CI job would
Promoting the email example into a validator

The Email-ish chip is [A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,} on [email protected] x not-an-email [email protected]. With g it highlights [email protected] and [email protected]. It is a demo, not RFC 5322, and the page says so. The same honesty applies to sticky mode: flag y matches only at the start of the test string here, because lastIndex is reset on every run. It will not “continue where the last match stopped” across clicks. And results do not update while you type. Changing the pattern, the flags, the mode, or the text clears the cards until you press Test again.

Shipping the email-ish pattern as form validation
Expecting flag y to resume from the previous match
Editing the pattern and reading the stale cards that just disappeared as a failure of the engine
Turning g off and wondering why a string with three dates shows a single card

What each flag and mode actually changes here

g, i, m, s, u, y

Six flags, no others. Default is g alone.

g — global. Every non-overlapping match. Off: the first match only. matchAll in JavaScript also requires g
i — ignore case. foo against FOO misses until i is on
m — multiline. ^ and $ match at line edges. The chip ^\w+: on “name: Ada” plus “age: 36” needs g and m to catch both labels
s — dotAll. The dot matches newlines. Without s, a pattern that must cross a line break fails even when the text looks continuous
u — Unicode. Turns \p{L} into a property escape that matches letters. Without u, the same source matches the literal text p{L}: it compiles, and it does not match é
y — sticky, but lastIndex is reset every run, so the match has to start at index 0 of the test string. On “ba” the pattern a with y matches nothing; on “ab” it matches the leading a and stops
Match cards versus a replace preview

Match lists hits. Replace also shows the substituted string, using JavaScript replacement patterns.

Match: each card shows the index, the text, and groups. An empty (zero-width) match is labeled (empty), not omitted
Highlights never overlap: the first match wins, later overlaps are skipped in the painted string
Replace: $1 is group 1, $& is the whole match, $<name> is a named group. The chip (\w+)=(\d+) with [$1=$2] on a=1 b=2 becomes [a=1] [b=2]
The docs' shorter case, (\d+) replaced by [$1] on x=10, becomes x=[10]. The substitution is a preview, not a file rewrite
Numbered groups, named groups, and the 500 cap

A named capture is the same group as its number. The card shows it once.

The date chip (?<year>\d{4})-(?<month>\d{2})-(?<day>\d{2}) on “shipped 2024-03-15 ok” yields GROUP 1 (year), GROUP 2 (month), GROUP 3 (day) — not a second unlabeled row
The pairs chip (\w+)=(\d+) on “user=42 role=admin env=prod” yields one match, user=42. role=admin is not digits. That is the example working as written, not a bug
Digits chip \d+ on “order-1042 total=89” yields 1042 and 89
Above 500 matches the list is sliced and a warning says results were truncated (soft cap or loop guard). The warning is the signal to narrow the pattern or the subject, not to trust a partial scan as complete

Run one case, then change a single variable

Pattern, flags, subject, Test

On the regex tester, nothing runs while you type. The previous cards clear as soon as the inputs change.

1

Write the source, not a literal

Type \d+, not /\d+/g. If you do paste a /pattern/flags literal, paste or blur unwraps it and merges flags. Empty pattern is an error. Invalid pattern shows the JavaScript message.

2

Toggle only the flag you mean to test

g is on by default. Turn it off to confirm you really wanted a single match. Add m before trusting ^ on a second line. Add u before \p{…} — without it, \p{L} matches the literal characters p{L}, not letters. Leave y off unless you are checking a match that must start at index 0.

3

Press Test and read the card, not just the highlight

The highlight shows where the hit sits. The card shows the index and each group. Click a card to emphasize that hit. In replace mode, read the substituted string under the cards before copying the pattern into code.

Examples, tokens, and a local file

The chips are fixtures, not a library of production validators.

Examples: key=value pairs, digits, named date, email-ish, multiline ^, replace groups. Choosing one fills the fields and runs immediately
Sample does the same with the pairs fixture: (\w+)=(\d+) on user=42 role=admin env=prod
Cheat sheet inserts \d, \w, \s, ., [], ^, $, \b, +, *, ?, and () at the caret. Brackets and parentheses place the caret inside
Upload reads a .txt, .md, or .log file up to 32 MiB into the test string, in the browser. Prefer a short excerpt when the pattern is not obviously linear
Clear resets the pattern, the text, the replacement, the mode, and the flags back to g

The same engine in Node

Collect matches the way the tester does

Node's RegExp is the same language. matchAll requires the g flag. Without g, use exec once.

Basic example

const subject = 'user=42 role=admin env=prod' const re = /(\w+)=(\d+)/g const matches = [...subject.matchAll(re)] // length 1 — role=admin is not digits // matches[0][0] === 'user=42' // matches[0][1] === 'user' // matches[0][2] === '42'
Preview a replacement before it edits a file

Replacement patterns are the ones the replace field accepts. This is the built-in chip, not a custom template language.

Basic example

const out = 'a=1 b=2'.replace(/(\w+)=(\d+)/g, '[$1=$2]') // [a=1] [b=2] const wrapped = 'x=10'.replace(/(\d+)/g, '[$1]') // x=[10]
FastMinify regex-tester

No install, nothing uploaded, flags and groups visible, replace preview included. Trade-off: no flavor switch, sticky mode only at index 0, at most 500 returned matches, and a nested quantifier can freeze the tab because execution is local. Keep the page for a fixture and a code review. Commit the pattern beside tests when it guards production input. If the expression was only standing in for a slug or a case change, switch to slugify or the case converter instead of maintaining it.

Conclusion

Test the pattern against a string that should fail, in the engine that will run it. For JavaScript that engine is RegExp, which is what the regex tester uses — including a JSON Schema pattern you will later compile with Ajv in the JSON Schema validator. Do not treat the email-ish chip as a validator, do not expect flag y to resume mid-string, and do not feed a nested quantifier a 32 MiB log. When the job is a slug or a case change, leave the expression alone and use the dedicated tool on the developer utilities hub.

JavaScript RegExp only — a PCRE snippet that the tester rejects will also fail in Node
Press Test after each edit; the cards do not follow the caret
Greedy .+ between quotes is one match; lazy is every shortest pair, not a semantic filter
Named groups appear once. More than 500 hits is a truncated list, not a complete scan
Flag y matches only at the start of the test string in this tool
Share this article
Share this article: