Regex Tester
Test regular expressions with live match highlighting and group capture.
Highlighted (2 matches)
Match details
- Group 1:
hello - Group 2:
example - Group 3:
com
- Group 1:
sales - Group 2:
example - Group 3:
org
This tool runs a pattern against a block of text using the browser’s own JavaScript RegExp engine, highlighting every match on every keystroke. Write the pattern between the two slashes, add flags in the box beside it, and edit the test string below — there’s no run button, because every keystroke re-evaluates the match live. It exists for the step before you commit a regex to actual code: catching a pattern that matches more than you meant, less than you meant, or simply won’t compile.
Quick reference
| Field | What it does |
|---|---|
| Pattern | The regex body, typed without the surrounding slashes |
| Flags | Free-text field; g is applied automatically even if you never type it |
| Test string | Any text; the highlight and match list update on every edit to either field |
| Match details | Shown per match: the starting index, the matched text, and every captured group, numbered Group 1, Group 2, … |
| Invalid pattern | A red banner shows the exact message your browser’s RegExp constructor throws |
| Rewriting matches | Not built in here — this page only tests; find and replace is the tool that substitutes text |
What do the regex flags actually do?
The flags field is free text, and whatever you type goes straight into JavaScript’s RegExp constructor, so this tester accepts every flag the engine does. Six of them change what you see here:
i— case-insensitive./error/imatchesERROR,ErrorandeRRoRalike.g— every match, not just the first. This one is special: the tester always searches withgincluded, even if you leave it out of the field, so you can’t accidentally see a single result here.m—^and$match at the start and end of each line, not just the start and end of the whole string. Without it,/^b$/mnever matches inside"a\nb"; with it, it matches the second line.s— dotall. Normally.refuses to match a line break;smakes it match line breaks too.u— unicode-aware. Without it, a dot or a\u{...}code-point escape can silently split an emoji or another astral character into two meaningless halves; with it, the engine treats the full character as one unit.y— sticky. Each match must begin exactly where the previous one ended. Handy for tokenizing a string piece by piece — see Common mistakes for what happens the moment a gap breaks that chain.
How do I capture part of a match?
Wrap any part of your pattern in parentheses and it becomes a capture group. (\d{4})-(\d{2})-(\d{2}) against 2026-08-08 produces one full match plus three groups — year, month, day — and Match details lists all three, numbered left to right: Group 1, Group 2, Group 3. A group that didn’t fire in a particular match, because it sat inside an optional ? that wasn’t used, shows as (none) rather than an empty string, which is a useful way to tell “captured nothing” apart from “captured an empty string.”
You can also name a group with (?<year>\d{4}) — the engine parses the syntax and captures the value correctly — but this page never surfaces the name. It still lands in the list only as a numbered Group 1, in the same order an unnamed group would use. If the name matters for documentation, keep it in a comment next to the pattern, because nothing here prints it back.
Captured text keeps whatever case it had in the source string. If a downstream step needs it in a different case — pulling a product code out of a log line and needing it uppercase for a lookup, say — run the extracted value through the case converter after you’ve confirmed the group captures the right substring.
Use (?:...) when you want grouping without a number — typically for alternation, or to apply a quantifier to a whole chunk of pattern. (?:https?|ftp):// groups the scheme for alternation but adds nothing to Match details, so the groups you actually care about keep their expected positions.
Greedy vs lazy quantifiers
*, + and {n,m} are greedy by default: each one grabs as much text as the rest of the pattern still allows. Against <a><b>, the pattern <.+> matches the entire string, not just <a>, because .+ grabs everything up to the end first and only backs off one character at a time until a trailing > satisfies the rest of the pattern — and the very last > in the string does that on the first try. Put a ? right after the quantifier — <.+?> — and it flips to lazy: it takes the minimum first and only takes more if the rest of the pattern can’t match without it, so the same input now matches just <a>.
Greedy and lazy only decide which match wins when more than one is possible; they don’t decide whether a match happens at all. The gotcha to watch for is a quantifier that can match zero characters, like a* or \s*. Run a* globally against baab and Match details shows four entries, not two: an empty match at index 0 (before the first b), aa at index 1, and two more empty matches at indices 3 and 4. Nothing is broken — the search has to step forward one position after a zero-length match so it doesn’t loop on the same spot forever, and each of those steps counts as its own match. If a match list looks longer than expected, check whether any entry’s captured text is actually blank before assuming the pattern itself is wrong.
Common mistakes
Forgetting to escape a literal special character. ., $, (, ), [, ] and a handful of others all mean something to the regex engine even outside a character class. Write a literal price as $19.99 without escaping it and the leading $ is read as an end-of-string anchor — since nothing can ever come after “the end of the string,” the pattern is unsatisfiable and matches nothing, on any input, ever: 'costs $19.99 today'.match(/$19.99/) returns null, silently, with no error to warn you. That’s a different failure than the unescaped . in the same pattern, which is a real over-matcher rather than a dead end — on its own, 19.99 also matches 19x99 or 19_99, because . accepts any single character. Escape both to mean what they look like: \$19\.99.
Adding the sticky flag and expecting it to behave like global. y refuses to skip ahead: the moment there’s a character between two potential matches that the pattern doesn’t also consume, the search stops instead of hunting for the next one. A pattern that returns three matches with g can return exactly one with y, purely because of a gap between the first match and the second.
Typing a flag combination the engine rejects. Repeating a letter (gg) or using one JavaScript doesn’t recognize produces “Invalid flags supplied to RegExp constructor” in the red banner — no match count at all, because the flag string itself failed before your pattern was ever compiled.
Reaching for a pattern instead of an actual parser or decoder. A regex that “mostly” matches JSON breaks the moment a string value contains an escaped quote or a nested object; use the JSON formatter for that, since it parses properly and reports exactly where it fails. Likewise, a pattern that matches a URL doesn’t decode the percent-encoded parts inside it — %20 stays %20 no matter how the capture group is written, so run the extracted string through the URL encoder/decoder for that step.
Trusting the match count as proof the whole job is done. This page only tells you a pattern matches; it doesn’t rewrite anything. Once the same pattern is doing real work in find and replace or elsewhere, confirm the actual before-and-after with the text diff checker rather than assuming a clean match count here means the output is correct too.
Privacy
A pattern can give away a secret’s shape even when it contains no secret at all: ^sk_live_[A-Za-z0-9]{32}$ tells anyone reading it exactly how your live API keys are structured, and \d{3}-\d{2}-\d{4} is unmistakably a social security number check. The test string tells its own story — the box starts with a placeholder email address, but the whole point of testing is to try the pattern against something closer to what it’ll actually see, so people paste in a line pulled from a real access log, an actual customer phone number, or a row copied out of a spreadsheet they’re trying to scrub. Both end up sitting in the same three fields, and this component makes no network call with any of them: pattern, flags and test string live in local component state and get run straight through RegExp.prototype.exec on every keystroke, in your tab, on your machine. There’s no batching, no “send when ready,” because there’s nothing built to send it — refresh the page and all three fields are just gone.
Frequently asked questions
- Which regex flavor does this tester use?
- JavaScript (ECMAScript) — the flavor built into every browser's own
RegExpobject, and into Node.js. It differs from PCRE, the flavor most server-side languages default to: named-group syntax, look-behind support and some character-class escapes vary between the two, so a pattern that works here is still worth checking against your actual runtime if that runtime isn't JavaScript. - Why does my match list show entries with no text in them?
- A quantifier that can match zero characters —
a*,\s*,.*— is allowed to match an empty string, and this tester counts every one of those as a real match. Runa*againstbaaband you get four entries: an empty match before the firstb, thenaa, then two more empty matches. Check whether the surprising entries are actually blank before assuming the pattern is broken. - Can I use named capture groups like (?<year>\d{4})?
- You can write them, and the JavaScript engine captures the value correctly, but this tester only lists groups positionally — Group 1, Group 2 and so on — it never prints the name back to you. If the name matters for your own code, keep it in a comment next to the pattern.
- Why did my match count drop to one after I added the sticky flag?
- The
yflag requires each match to start at exactly the position the previous one ended, with no gap in between. The moment a character sits between two potential matches that the pattern itself doesn't consume, the search stops rather than skipping ahead — so a pattern that finds several matches withgalone can find just one onceyis added, if those matches aren't back-to-back in your test string.
Related tools
HTML / CSS / JS Minifier
Minify HTML, CSS and JavaScript to reduce file size for production.
AWS EventBridge Cron Generator
Generate 6-field AWS EventBridge cron expressions, see the next UTC run times, and copy ready-to-paste CloudFormation, Terraform and CLI snippets.
Cron Expression Builder
Build cron expressions for Unix, Kubernetes, AWS EventBridge and Quartz — with a human-readable description and the next 5 run times.
Kubernetes CronJob Schedule Generator
Build Kubernetes CronJob schedule strings, preview the next runs, and copy a complete CronJob YAML manifest.
JSON Formatter & Validator
Pretty-print, minify and validate JSON with line-accurate error messages.
JWT Decoder
Decode a JWT token to view its header, payload and expiry — client-side only.