Regex Tester

Test regular expressions with live match highlighting, group capture, and replacement preview.

//g
Flags:
Examples:
Test string
Replace (optional)

About This Tool

What this tool actually does

Regex Tester takes a regular expression you type, runs it against your own sample text, and shows you what matched, where it matched, and what each capturing group caught. If you want to see what a search-and-replace would produce, you can type a replacement string and the rewritten text appears underneath. Everything updates as you type, and nothing ever leaves your browser.

One thing to be clear about up front: this tool uses the JavaScript (ECMAScript) regex engine, because that's what your browser ships with. If you're writing Python re, PCRE, Go, Java, or .NET regex, most basic syntax overlaps, but flavor-specific features will not behave the same here. More on that further down.

Who actually needs this

If you're writing JavaScript or TypeScript and need a regex for a form validator, a string extraction, or a .replace() call, this is the most honest sandbox you can use — the engine running here is literally the engine that will run in your app. If you're writing Python or Go and your regex is using the common subset (character classes, quantifiers, basic groups, lookahead), you can prototype here and port the pattern over, just don't rely on features that don't exist in JavaScript.

Other times it earns its keep:

  • Cleaning a log file or CSV and you need to test the pattern against a few real rows before you let it rip on the whole thing.
  • Pulling all email addresses, URLs, or phone numbers out of a chunk of text.
  • Sanity-checking a regex someone else wrote that you don't fully trust.
  • Building a find-and-replace where the replacement uses captured groups (turning FirstName LastName into LastName, FirstName, for example).
  • Learning regex. Live highlighting on text you understand is a much faster feedback loop than reading docs.

How to use it

The interface has four parts: a pattern field, four flag toggles, a test-string box, and an optional replacement field.

Pattern. Type the body of your regex into the Pattern field. Don't wrap it in slashes — the / characters drawn on either side of the input are visual decoration that mirrors JavaScript's literal regex syntax (/foo/g). The flags are set with the toggle buttons, not by typing them after a slash. So if you want /\d{4}-\d{2}-\d{2}/g, you type \d{4}-\d{2}-\d{2} into the box and click the g button.

Flags. Four toggle buttons sit just below the pattern: g, i, m, s. Click any of them to turn it on or off. By default g starts on.

  • g (global) — find every match, not just the first. Almost always what you want when you're enumerating results.
  • i (ignore case) — cat also matches Cat, CAT, etc.
  • m (multiline) — ^ matches the start of every line and $ matches the end of every line, instead of just the start and end of the whole string.
  • s (dotAll) — lets . match newline characters too. Without this, . matches anything except line breaks.

Other JavaScript flags (u, y, d) aren't exposed here, but the four above cover roughly 95% of practical regex work.

Examples. Below the flag toggles is a row of one-click examples: Email, URL, Phone (US), IPv4, and Hex color. Click one and both the pattern and the flags populate. These are good starting points, not battle-tested validators — the email pattern, for instance, matches a generous but loose definition of an email address, which is appropriate for finding emails in messy text but not for validating user input.

Test string. Type or paste your sample text into the textarea. Matches are highlighted in yellow directly in the rendered preview below the textarea, and a badge in the corner shows the match count. The badge stays gray when there are zero matches and turns prominent once at least one match lands.

Matches list. Below the test string, every match appears on its own numbered row with three pieces of information: the matched text, its index (the zero-based character position in the test string where the match starts), and, if your pattern has capturing groups, a groups: [...] entry showing what each set of parentheses captured.

Replacement. The last card is optional. Type a replacement string and the rewritten text appears below it. Use $1 for the first captured group, $2 for the second, and so on. $& inserts the whole match and $$ produces a literal dollar sign. The preview only shows up once the replacement field has something in it.

How it works under the hood

The tool builds a RegExp object using new RegExp(pattern, flags), then iterates results using the engine's exec() method. There's a small but important detail in how enumeration works: to collect every non-overlapping match it forces the g flag on for the iteration, regardless of what you've toggled. If you haven't turned on g yourself, it advances through the input once, captures the first match, and then breaks out of the loop. With g on, it keeps stepping until exec() returns null.

For the replacement preview, the engine uses your pattern exactly as configured — including whatever flags you actually have on. That means if g is off, only the first match gets replaced, even though the Matches list might still only show one entry (since the list also stops at one without g). This is consistent with how JavaScript's String.prototype.replace() actually behaves in your app, which is the point.

Matching is left-to-right and non-overlapping. Once characters have been consumed by a match, the next search starts after them. So a pattern like aa against the string aaaa finds two matches, not three — the second match begins at index 2, not index 1. Lookaheads can sometimes help you find overlapping things, but the engine itself won't double-count.

Capturing groups follow standard JavaScript rules: (...) creates a numbered capture you can reference as $1, $2, etc.; (?:...) is non-capturing (use it when you need to group for alternation or quantifiers but don't care about the captured text); (?<name>...) creates a named group you can reference as $<name> in the replacement.

If the pattern is invalid — an unbalanced parenthesis or bracket, a quantifier with nothing to attach to, a stray \ at the end — the engine throws a SyntaxError and the tool surfaces the message in red below the pattern field. Matching pauses until the pattern is valid again. That's a feature: it stops you from chasing a phantom result and tells you exactly what's wrong.

An empty pattern returns zero matches by design, so the tool doesn't pretend an empty string matches everywhere (which is technically what a true empty regex does in some engines, and would just be noise here).

Everything runs locally in the browser. The pattern, the test text, and the replacement string are never sent to a server, so you can paste real production data or sensitive logs in without worrying.

A worked example: pulling timestamps out of a log

Say you have a log file fragment like this in the test string:

2024-11-03 14:22:01 ERROR auth failed for user alice
2024-11-03 14:22:08 INFO retry succeeded for user alice
2024-11-03 14:25:14 WARN slow query: 1.4s

You want to extract every timestamp and split it into a date part and a time part. Pattern:

(\d{4}-\d{2}-\d{2}) (\d{2}:\d{2}:\d{2})

Flags: g on (you want all of them).

The Matches list will show three rows. Each one looks like:

1 2024-11-03 14:22:01 index 0 groups: [2024-11-03, 14:22:01]

To rewrite the lines as [date] time, type this in the Replace box:

[$1] $2

The preview will show the test string with every 2024-11-03 14:22:01 rewritten to [2024-11-03] 14:22:01. If you turn off g and rerun the replacement, you'll see only the first line changes — which is exactly what would happen if you copied this pattern into a JavaScript str.replace(re, "[$1] $2") call without g.

Common pitfalls

Forgetting g when you want all matches. The most common confusion. If you see one match where you expected ten, that's the first thing to check.

Forgetting to escape special characters in literal text. If you want to match the literal string 192.168.1.1, the dots will match any character unless you escape them. Either escape each one (192\.168\.1\.1) or use a character class ([.]).

Trying to use Python or PCRE-only syntax. Inline flag modifiers like (?i) don't work in JavaScript — use the i toggle instead. \A and \Z anchors don't exist — use ^ and $ (with the m flag if you need per-line behavior). Recursion ((?R), (?1)) and possessive quantifiers (a++) don't exist either. If a pattern works in Python but throws an error here or matches nothing, a flavor mismatch is almost always why.

Greedy versus lazy quantifiers eating too much. .* is greedy — it grabs as much as it can. If you're trying to extract content between two quotes and your pattern is "(.*)", against he said "hi" and "bye" the group will capture hi" and "bye. Use lazy quantifiers (.*?) or a negated character class ("([^"]*)") instead.

Trying to match across lines without s. By default, . does not match newline characters. If your test string spans multiple lines and you want a pattern to flow over the line break, turn on the s (dotAll) flag.

Confusing ^ and $ behavior. Without the m flag, ^ only matches the very start of the test string and $ only matches the very end. Turn on m if you want them to match the start and end of every line.

Assuming a complex regex validates emails or URLs properly. The Email and URL example patterns are good for finding likely candidates in free text. They are not RFC-compliant validators. If your form needs to validate that something is a usable email address, prefer to send a confirmation email rather than rely on a regex.

Sensible defaults when you're starting from scratch

Some starting points that get you 80% of the way to what you usually want:

  • Digits only: \d+ for one or more, or \d{4} for exactly four (useful for years, PINs).
  • Whole words: \b\w+\b — the \b word boundaries stop you from matching across a token boundary.
  • Whitespace-separated tokens: \S+ matches any run of non-whitespace.
  • Anything between two markers: start(.*?)end with the s flag if it can span lines. The ? after * makes it lazy.
  • A list of alternatives: (cat|dog|bird). Put the longest match first if any are prefixes of others.
  • "Or nothing": colou?r matches both color and colour. ? means zero or one of the preceding token.

When NOT to use a regex tester (or a regex at all)

Regex is a hammer, but not every text problem is a nail.

  • Parsing HTML or XML. Famously, nested and arbitrary nested structures aren't a job regex can do correctly. Use a real parser (DOMParser, cheerio, BeautifulSoup) instead. Regex is fine for grabbing values out of HTML you control and trust, less fine when the structure can vary.
  • Parsing JSON. Same reason. Use JSON.parse.
  • Validating email addresses for business logic. A regex that's strict enough to reject malformed emails will also reject some valid ones (the RFC allows quoted local parts, plus addressing, internationalized domains). For login flows, do a permissive regex check at most and verify with a confirmation email.
  • Heavy text transformations across many rules. If you're chaining six regex replacements, consider a tokenizer or template engine. Regex chains become unreadable quickly.
  • When a simple indexOf or split works. If you're just looking for a literal substring, you don't need a regex. String.prototype.includes() is faster and clearer.

Adjacent concepts worth knowing

Capturing vs. non-capturing groups. (foo) captures into $1. (?:foo) groups for quantifiers and alternation but doesn't capture. Use non-capturing when you don't need the value — it makes patterns clearer and slightly faster.

Lookahead and lookbehind. foo(?=bar) matches foo only when followed by bar, but doesn't include bar in the match. (?<=bar)foo matches foo only when preceded by bar. Negative versions are (?!...) and (?<!...). Modern JavaScript engines support both.

Anchors. ^ start of string (or line, with m), $ end of string (or line), \b word boundary. Anchors don't consume characters — they're assertions about position.

Character classes. [abc] matches any of a, b, or c. [a-z] matches a range. [^abc] matches anything except a, b, or c. \d, \w, \s are shorthand for digit, word character, and whitespace; their uppercase versions are the inverses.

Quantifiers. * zero or more, + one or more, ? zero or one, {n} exactly n, {n,m} between n and m, {n,} at least n. Add a ? after any of these to make it lazy (match as little as possible).

Catastrophic backtracking. Some patterns, especially ones with nested quantifiers like (a+)+, can take exponential time on inputs that almost-but-don't-quite match. If your browser hangs on a pattern, this is usually why. Restructure the pattern to be unambiguous about what each character belongs to.

What to do if the result looks wrong

A short checklist when matches aren't what you expect:

  • Match count too low? Check g. Without it, the list and the highlighter both stop at the first match.
  • Match count too high? Your pattern is probably matching less than you think it is. Look at the matched text in the Matches list — what's actually getting captured tells you what to constrain. Word boundaries (\b) or anchors often help.
  • Match is bigger than expected? Greedy quantifier eating too much. Try ? after the quantifier to make it lazy, or replace .* with a negated character class.
  • Capturing groups look wrong? Walk through your parentheses. Group numbers count opening parens left to right. (?:...) doesn't increment the count.
  • Error message in red? Read it — JavaScript regex errors are usually specific. Common ones: Unterminated group (missing )), Unterminated character class (missing ]), Invalid escape (stray \ at the end).
  • Pattern works in Python/PCRE but not here? Almost always a flavor difference. Replace inline flags like (?i) with the toggle buttons, replace \A/\Z with ^/$, replace possessive quantifiers with atomic-group equivalents if available.
  • Replacement only changes one match? Same fix as the first item: turn on g.

Privacy

The pattern, test text, and replacement string are processed entirely in your browser using the local JavaScript regex engine. Nothing is uploaded, logged, or stored on a server. You can safely test against real production logs, internal data, or anything else you would otherwise be cautious pasting into a hosted tool.

The about text and FAQ on this page were drafted with AI assistance and reviewed by a member of the Coherence Daddy team before publishing. See our Content Policy for editorial standards.

Frequently Asked Questions

Do I need to wrap my pattern in slashes like /pattern/?
No. Type only the pattern itself, for example \d{4}-\d{2}-\d{2}. The slashes drawn on either side of the input are visual decoration that mirrors JavaScript's literal-regex syntax. Flags are set with the toggle buttons rather than typed after a slash.
Which regex flavor does this use? Will my Python or PCRE pattern work?
It uses the JavaScript (ECMAScript) engine, the same one your browser runs. Most common syntax overlaps: character classes, quantifiers, anchors, capturing and non-capturing groups, lookahead, and lookbehind all work. But flavor-specific features differ. JavaScript has no inline flag modifiers like (?i), no \A/\Z anchors (use ^ and $), no recursion, and no possessive quantifiers. Named groups use (?<name>...) with $<name> in replacements. If a pattern works in Python or PCRE but errors here, a flavor mismatch is almost always why.
Why does my pattern only show one match even though there are several?
The g (global) flag is probably off. Without g, both the Matches list and the replacement preview stop after the first match. Click the g toggle button and the rest will appear.
What does each row in the Matches list mean?
Each numbered row shows three things: the matched text (the entire slice the whole pattern matched), its index (the zero-based character position where the match begins), and, if your pattern has capturing groups, a groups: [...] entry listing what each pair of parentheses captured, in order. Rows for patterns without capturing groups simply omit the groups entry.
How do I reference captured groups in the replacement?
Use $1 for the first group, $2 for the second, and so on. $& inserts the entire match, and $$ produces a literal dollar sign. For example, replacing (\w+)\s(\w+) with $2 $1 swaps two adjacent words. The replacement preview only appears once you've typed something in the Replace box, and if g is off only the first match is replaced.
My pattern works in Python but errors here. Why?
JavaScript regex doesn't support some Python-only syntax. The biggest gotchas: inline flag modifiers like (?i) at the start of a pattern, \A and \Z anchors, named groups using Python's (?P<name>...) syntax (JavaScript uses (?<name>...)), and conditional groups. Switch to JavaScript-native equivalents and you should be set.
Why does the test string sometimes show no highlighting even though there are matches?
Highlighting only renders when the pattern is non-empty, there's no error, and the match list isn't empty. If you have a match shown in the Matches list but no highlight, something else is off; usually a console error in the page. More common case: there's an error message in red below the pattern field, which pauses matching until the pattern is valid.
Is the s flag the same as the multiline flag?
No, they're different. The m (multiline) flag changes how ^ and $ behave: with m on, ^ matches at the start of every line and $ matches at the end of every line, rather than just the start and end of the whole string. The s (dotAll) flag lets the . metacharacter match newline characters; without s, . matches anything except line breaks. You can turn either or both on independently.
Can I match overlapping occurrences?
Not directly. The engine matches left-to-right and consumes characters as it goes, so once a match is found the next search starts after it. A lookahead trick like (?=(aa)) lets you find overlapping positions by capturing the look-ahead text instead of consuming it, but the highlighted match itself is the empty position. For most overlapping-search problems, a small loop in code is clearer than a regex.
Is my data sent anywhere?
No. The pattern, test text, and replacement string are all processed in your browser by the local JavaScript engine. Nothing is uploaded, stored, or logged on a server, so it's safe to use with sensitive sample text, real logs, or internal data.