Regex Tester
Test JavaScript regular expressions live: every match highlighted, every capture group itemised, every flag toggleable. Drop in a pattern from the curated library or write your own — and switch to replace mode when you need to transform text.
3 matches
Send the report to alice@checkfast.io or to the team@example.com. No email here. Try this one: bob+filter@test.io
| # | Index | Match | Groups |
|---|---|---|---|
| 1 | 19 | alice@checkfast.io | $1=alice$2=checkfast.io |
| 2 | 48 | team@example.com | $1=team$2=example.com |
| 3 | 99 | filter@test.io | $1=filter$2=test.io |
Learn More
This tool runs your pattern against the standard JavaScript regex engine — the same engine your browser Node.js Bun Deno and Cloudflare Workers all use. That means everything that works here works in your code without translation. JavaScript's regex flavour is documented in the ECMAScript spec (RFC TC39) and is reasonably close to PCRE for most everyday patterns: character classes quantifiers (* +? n m ) anchors (^ $ \b) alternation (|) grouping (capturing and non-capturing) lookahead and lookbehind named groups (?…) and Unicode property escapes (\p … ) under the u flag. Where JavaScript differs from PCRE: no possessive quantifiers (a*+ doesn't exist) no atomic groups no recursion and the named-group syntax is? rather than?P. If a pattern you wrote in PHP or Python doesn't compile here those are the usual reasons. The Library entries are all written in JS-compatible form so you can drop them into your code unchanged. The error messages this tool surfaces come straight from the engine — they're the same errors you'd see in a browser console so you can debug the regex once and ship it.
Six flags worth knowing. (g) global — required to find every match in a string; without it match() /.replace() only touch the first occurrence. We force g internally for the live preview so you see all matches but your code needs the same flag explicitly. (i) case-insensitive — straightforward but easy to forget. (m) multi-line — makes ^ and $ match per line instead of just the input ends; critical for processing multi-line text like log files. (s) dotAll — makes. match newlines too; without it dot stops at the line break which is rarely what you want for HTML or multi-line strings. (u) unicode — enables Unicode property escapes (\p Letter \p Emoji ) and stricter escape parsing; most modern apps should always set u. (y) sticky — the niche one only needed when you're hand-rolling a tokeniser via re.exec in a loop. Avoid the temptation to throw every flag on every pattern. Specifically m and s change semantics in ways that catch out other readers — only set them when the pattern actively uses them. The flag chips in the input let you toggle each one and watch the output change which is a faster way to internalise their effect than reading the spec.
Every pair of parentheses creates a capture group numbered 1 2 3 in left-to-right order. The full match is group 0 ($&). Named groups (?\d 4 ) are also numbered — naming doesn't replace the number it adds a label. In replace mode you can reference groups via $1 $2 $& $ $' and $<name>. Use named groups when the pattern has 4+ groups or when the order isn't obvious — code that reads $ is much harder to break than code that reads $3. Non-capturing groups (?:…) exist when you need grouping for alternation or quantification but don't want a capture slot allocated. They're free wins for performance and clarity — every non-capturing group you add is one less item in the captures array. Lookahead (?=…) and lookbehind (?<=…) don't capture either; they're zero-width assertions that the next/previous text matches without consuming it. Negative variants (?!…) and (?<!…) are similarly useful for excluding patterns. The match table on the right itemises every group's value per match including named groups so you can verify your pattern is splitting the input the way you intended.
Frequently asked questions
JavaScript / ECMAScript — the engine in V8 JavaScriptCore SpiderMonkey and every Node.js / Bun / Deno / Cloudflare Workers runtime. Patterns that compile here will compile in your application code without translation. The major differences vs PCRE: no possessive quantifiers (a*+) no atomic groups no recursion (?R) and named groups use (?…) instead of (?P…). For Python re.NET Go and Rust regex flavours ~95% of patterns work identically — the differences are usually in lookbehind support or specific class shorthands.
JavaScript named groups use? not?P. The (?P…) form is Python's syntax. Replace (?P\d 4 ) with (?\d 4 ) and the same pattern works. If you're porting from Python also replace backreferences: (?P=year) becomes \k and in replacements $ year becomes $. Most other Python regex syntax (alternation quantifiers character classes lookahead/lookbehind) translates 1:1.
Greedy (.*) matches as much as possible then backtracks until the rest of the pattern matches. Lazy (.*?) matches as little as possible then expands. The classic example: against hello the greedy <.*> matches the whole string but the lazy <.*?> matches just. Possessive quantifiers (.*+) exist in PCRE/Java but not JavaScript — they match greedily and refuse to backtrack which can make catastrophic-backtracking patterns safe. The JavaScript workaround is atomic-group-like behaviour via (?=(…))\1 — clunky but works for the rare cases where backtracking blow-up is a real problem (typically nested quantifiers like (a+)+).
Always in modern code. The u flag enables Unicode property escapes (\p Letter \p Emoji \p Script=Cyrillic ) strict escape-sequence parsing and proper handling of astral-plane characters (everything beyond U+FFFF — emoji CJK extension blocks mathematical symbols). Without u a regex like /😀/.test('😀') returns false on some engines and the engine treats the surrogate pair as two separate units. The cost of u is essentially zero — there is no performance penalty and almost nothing breaks. The exception is a small set of legacy patterns that relied on non-Unicode escape parsing (\u inside a class for example).
When a pattern can match the same input in multiple ways the engine tries each one. Nested quantifiers like (a+)+ on input 'aaaaaaa!' force the engine to try every possible split — exponential time in the input length. The pattern eventually fails (the!) but takes seconds or minutes to report failure on a string a malicious user supplied to your service. Avoidance: use atomic-group-like constructs (?=(pattern))\1 to commit to a match or — more practically — rewrite the pattern to be less ambiguous. The Library here is hand-checked against catastrophic backtracking. If you write patterns from scratch and run them on user-controlled input time-bound the call (lib/safer-regex npm packages or a Web Worker with an external timeout) so a bad pattern can't pin a CPU.
Two flags do different things often confused. (m) makes ^ and $ match line starts/ends instead of input ends — useful for matching per-line in a multi-line string. (s) makes. match newline — useful when you want to capture content that spans lines. To capture an HTML … block that may contain newlines you'd write /(.*?)<\/p>/s — without s the. stops at the first newline. Both flags are independent: m doesn't imply s and you can use one or both depending on what you need.
HTML: no. The grammar is recursive (tags can nest) regex (without recursion) cannot match nested structures correctly and the failure modes are subtle (unmatched closing tags case sensitivity attribute parsing). Use a real parser (DOMParser in browsers jsdom or parse5 in Node). JSON: technically possible for valid JSON but practically — use JSON.parse it's free and correct. Users no perfect regex (RFC 5322 needs ~6000 characters of pattern). The Library entry here matches 99% of real-world emails — the remaining 1% is RFC corners that almost no validator handles correctly and the right answer for production validation is send a confirmation email rather than a tighter regex.
More in Data Utilities
Developer validators, formatters and generators for structured data and identifiers.