Skip to content
FormatKit

Free JavaScript regex tester with an explain panel

Regex tester that cannot freeze your tab

This regex tester is free and needs no account: type a JavaScript regular expression and every match is marked in your test string as you type, with the numbered and named capture groups listed underneath and each piece of the pattern explained in plain English. All seven flags are one click away — g, i, m, s, u, y and d — replace mode resolves $1 and $<name> exactly as your code will, and split mode shows the array you would get back. Matching happens inside a Web Worker with a hard 1,000 ms limit on test strings up to 100,000 characters, so a pattern that backtracks for ever is stopped and diagnosed rather than taking the page down with it.

  • 100% free
  • No signup
  • All 7 flags
  • 1-second timeout
  • Up to 100,000 characters
335 characters
Matches in place
192.168.1.14 - alice [07/Aug/2026:11:02:41 +0000] "GET /api/orders?page=2 HTTP/1.1" 200 4213
10.0.0.8 - - [07/Aug/2026:11:02:43 +0000] "POST /api/orders HTTP/1.1" 201 87
203.0.113.42 - bob [07/Aug/2026:11:02:44 +0000] "DELETE /api/orders/9912 HTTP/1.1" 403 154
198.51.100.7 - - [07/Aug/2026:11:03:01 +0000] "GET /health HTTP/1.1" 200 2

Type a pattern above to start matching.

What this pattern says

  • (?<ip>start of capture group 1, reachable as $<ip> and groups.ip
  • \dany digit, 0 to 9
  • {1,3}repeated between 1 and 3 times, taking as many as it can
  • (?:start of a group that is not captured — it only holds things together
  • \.the character ., taken literally
  • \dany digit, 0 to 9
  • {1,3}repeated between 1 and 3 times, taking as many as it can
  • )end of the group
  • {3}repeated exactly 3 times, taking as many as it can
  • )end of the group
  • a single space
  • \Sany character that is not whitespace
  • +repeated one or more times, taking as many as it can
  • a single space
  • \Sany character that is not whitespace
  • +repeated one or more times, taking as many as it can
  • a single space
  • \[the character [, taken literally
  • (?<when>start of capture group 2, reachable as $<when> and groups.when
  • [^\]]any one character except: a literal ]
  • +repeated one or more times, taking as many as it can
  • )end of the group
  • \]the character ], taken literally
  • "the text " \"", exactly as written
  • (?<method>start of capture group 3, reachable as $<method> and groups.method
  • [A-Z]any one character from: A to Z
  • +repeated one or more times, taking as many as it can
  • )end of the group
  • a single space
  • (?<path>start of capture group 4, reachable as $<path> and groups.path
  • \Sany character that is not whitespace
  • +repeated one or more times, taking as many as it can
  • )end of the group
  • [^"]any one character except: "
  • *repeated zero or more times, taking as many as it can
  • " the text "\" ", exactly as written
  • (?<status>start of capture group 5, reachable as $<status> and groups.status
  • \dany digit, 0 to 9
  • {3}repeated exactly 3 times, taking as many as it can
  • )end of the group
  • a single space
  • (?<bytes>start of capture group 6, reachable as $<bytes> and groups.bytes
  • \dany digit, 0 to 9
  • +repeated one or more times, taking as many as it can
  • )end of the group
  • gglobalfind every match, not only the first
  • mmultiline^ and $ also match at every line break

Named groups: ip, when, method, path, status, bytes. In JavaScript they arrive on match.groups, and a replacement reaches them as $<ip>.

How to test a regular expression

Pattern, flags, test string — and an explanation of what you just wrote.

  1. Write the pattern and switch on the flags you need

    Type the expression between the slashes — no need to escape the delimiter, since you are not writing a literal — then click the flag pills underneath. Each one says what it does when you hover it, and the explain panel at the foot of the tool adds a line for every flag you turn on, so g stops being a letter and becomes “find every match, not only the first”.

  2. Read the matches, the groups and the explanation

    The right-hand pane shows your test string with every match marked in alternating colours, and empty matches drawn as a thin red bar so a stray \b or a* is visible rather than invisible. Click any row in the match list to load that match into the capture-group table, which names the group where you used (?<name>…) and gives start and end offsets once the d flag is on.

  3. Move to Replace or Split when matching is not the end of the job

    Replace applies your substitution string with $1, $<name> and $& resolved exactly as String.prototype.replace resolves them, and warns you when the missing g flag means only the first match changed. Split lists the array the pattern produces, including the capture groups JavaScript inserts between the pieces and the undefined entries a non-participating branch leaves behind.

Technical specifications

Regex engineYour browser's own RegExp object — the same implementation your JavaScript will run against, not a re-implementation with corner cases of its own
Flagsg, i, m, s, u, y and d, in any combination. The v flag is deliberately absent: it landed in Chrome 112, Firefox 116 and Safari 17, and a pattern relying on it breaks silently for anyone on an older build
Backtracking guardEvery attempt runs in a Web Worker built from a Blob URL and is terminated 1,000 ms after it starts; the page thread never executes the pattern
Measured speedA 100,000-character test string with \b\w+\b and the g flag produces 15,788 matches in roughly 1 ms — about a thousandth of the timeout
Maximum test string100,000 characters, near enough 100 KB of log lines or source
Matches reportedThe first 2,000 are highlighted and listed with their capture groups; counting continues to 50,000, and each match or group value is shown to 200 characters
Replacement syntax$1 to $99, $<name>, $&, $` , $' and $$ — whatever String.prototype.replace does, because String.prototype.replace is what runs
Processing locationYour browser: the pattern and the test string are never sent to a server, and the worker is same-origin

Frequently asked questions

Why does my regex hang the browser?

Because it is backtracking, not working. When a pattern can match the same text in more than one way — an open-ended repeat inside another open-ended repeat, as in (a+)+ — the engine tries every possible split before it is allowed to report failure, and the number of splits doubles with each extra character. Measured in V8 against a string of a characters ending in an x that can never match, (a+)+$ takes 98 ms at 24 characters, 392 ms at 26, 1.6 seconds at 28 and 6.3 seconds at 30. This page runs every attempt inside a Web Worker and terminates it after 1,000 ms, which is why you get a message here instead of a spinning tab.

What is a lookbehind, and where is it unsupported?

A lookbehind, written (?<=…) or (?<!…), asserts that something does or does not appear immediately before the current position without consuming it — (?<=\$)\d+ pulls 42 out of “$42” and leaves the dollar sign behind. JavaScript got it in V8 with Chrome 62 in 2017, Firefox 78 in 2020, and Safari only in 16.4 in March 2023, so an iPhone left on iOS 15 throws a SyntaxError on the entire pattern rather than ignoring the one construct. JavaScript's version is also unusually capable: the lookbehind may be variable length, where PCRE, Python's re and Java all demand a fixed width, so a pattern copied from this tool into those engines can fail for a reason that has nothing to do with your logic.

Why does my global regex skip matches or return null every other time?

Because a regex with the g or y flag carries a lastIndex property that survives the call. Calling re.test(s) twice in a row on the same object returns true then false, since the second call resumes from where the first stopped and runs off the end; the same trap catches re.exec inside a loop over an array, and a regex declared as a module-level constant is the usual victim. Either build the regex where you use it, set re.lastIndex = 0 before each run, or use s.match(re) which does the reset for you.

Why did my capture group come back undefined instead of an empty string?

Because a group that never took part in the match has no value at all, and JavaScript reports that as undefined. In (a)|(b) exactly one of the two groups participates in any given match, so the other is undefined — not "" — and code that does m[2].length crashes rather than measuring zero. An optional group behaves the same way: (\d+)? that matched nothing is undefined, while (\d*)? that matched nothing is the empty string, and the tables on this page distinguish the two.

Does the dot match a newline, and what should I use if it must?

No — the dot matches every character except the four line terminators \n, \r, \u2028 and \u2029. Turn on the s flag, dotAll, and it matches those too; that flag arrived in ES2018 and is safe everywhere today. The older workaround still found in production code, [\s\S] or [^], works by asking for a character class that has no exception, and it survives in patterns that must run through a build target predating the flag.

Why does my emoji pattern match half a character?

Because a JavaScript string is a sequence of UTF-16 code units, and anything above U+FFFF occupies two of them. Without the u flag the dot matches one code unit, so /./ against a grinning face returns a lone surrogate that renders as a broken box, and "😀".length is 2 rather than 1. Add u and the pattern works in code points, which also unlocks \u{1F600} and property escapes such as \p{Letter} or \p{Emoji_Presentation}. Note that even a code point is not always a whole character — a family emoji or an é written as e plus a combining accent is several code points that render as one grapheme.

Will a pattern I test here work in Python, Go or grep?

Often, but four differences bite most: Go's standard regexp package and RE2 refuse backreferences and lookarounds outright, in exchange for a guarantee that matching is linear in the input and can never blow up the way JavaScript's can; POSIX grep needs -E before it will read +, ? or | the way you wrote them; Python's \d matches Devanagari and Arabic-Indic digits unless you pass re.ASCII, while JavaScript's \d is always exactly 0-9; and PCRE offers atomic groups (?>…) and possessive quantifiers such as a++ that JavaScript simply does not have, which is precisely why catastrophic backtracking is easier to hit here than there.

About testing regular expressions

A JavaScript regular expression is not a machine that scans your text once. It is a backtracking engine that walks the pattern, and whenever a step could be taken in more than one way it remembers the alternatives so it can come back and try them. That is what makes backreferences and lookarounds possible, and it is also the bill: for a pattern like (a+)+b the inner and outer repeats can carve the same run of characters up in exponentially many ways, and each one has to be tried before failure can be declared. The industry has the scars to prove it. Cloudflare took its own global network offline for around half an hour on 2 July 2019 when a new firewall rule containing .*(?:.*=.*) pinned every CPU it ran on, and Stack Overflow went dark for 34 minutes on 20 July 2016 because one post began with about 20,000 spaces and met a regex that trimmed trailing whitespace. Neither pattern looks dangerous. That is the point, and it is why this page never runs your pattern on the thread that draws the page.

Most slow patterns are fixed by removing an ambiguity rather than by cleverness. If two parts of the pattern can both consume the same character, decide which one owns it: ".*?" between quotes becomes "[^"]*", which cannot backtrack because the class excludes the terminator. Collapse (a+)+ to a+, which matches the same language in one pass. Anchor with ^ so failure is decided at one starting position instead of every position in the string. Put an upper bound on repeats that should not be open-ended — {1,64} instead of + for an identifier. The explain panel flags the two shapes worth distrusting: an unbounded repeat nested inside another, and a repeated alternation whose branches can match the same text.

The other half of using regex well is knowing when to put it down. A regular expression cannot count nested brackets, so validating a payload with one is a losing game — paste it into the JSON checker and let a parser answer instead. Extracting one field from a line of known shape is exactly the right job for it; reformatting a whole document is usually not. And when what you actually want is to see how two versions of a file differ, a line-by-line comparison answers in a second what a hand-built pattern spends an afternoon approximating.

Where your pattern is run

The pattern and the test string stay inside this tab. The worker that executes them is built from a Blob URL created in your own browser, so there is no upload, no logging and no request carrying your data anywhere — the network panel stays empty while you type. It also means the tester keeps working with the connection off, and that closing the tab disposes of everything you pasted.