Regex Tester
Test regular expressions with live match highlighting, group capture, and replacement preview.
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 LastNameintoLastName, 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) —catalso matchesCat,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\bword boundaries stop you from matching across a token boundary. - Whitespace-separated tokens:
\S+matches any run of non-whitespace. - Anything between two markers:
start(.*?)endwith thesflag 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?rmatches bothcolorandcolour.?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
indexOforsplitworks. 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/\Zwith^/$, 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.