StringToolsStringTools

Regex Tester

Test regular expressions against sample text in real-time with live match highlighting and capture group inspection.

Mitul MandankaFounder, Progragon Technolabs · 15+ years building software
Updated June 20269 min read
//g
Test String
Match Preview
Highlighted matches will appear here...

TL;DR

This tester runs your pattern through the browser's native JavaScript (ECMAScript) RegExp engine — not PCRE, not POSIX. That matters: lookbehind, possessive quantifiers, atomic groups, and inline flags like (?i) behave differently or do not exist here versus PHP, Python, or grep. Toggle the g i m s u y flags to change matching behavior. Watch out for nested quantifiers like (a+)+ — they can cause catastrophic backtracking (ReDoS) and freeze the tab on certain inputs.

Why the same regex behaves differently in JavaScript, PCRE, and POSIX

A "regex flavor" is the specific dialect a given engine implements. A pattern that works in your PHP backend (PCRE) can throw a syntax error or silently match the wrong thing in this in-browser tester (ECMAScript). These are the differences that bite people most often:

FeatureJavaScript (this tool)PCRE (PHP, R)POSIX ERE (grep -E)
Lookbehind (?<=...)Yes (since ES2018)YesNo
Inline flags (?i)No (use the flag toggles)YesNo
Atomic groups (?>...)NoYesNo
Possessive quant. a++NoYesNo
Named groups(?<name>...)(?<name>...)No
Unicode property \p{L}Yes, with u flagYesNo
\d meaningASCII 0-9 (Unicode digits only with u+\p)ASCII by defaultASCII (locale-dependent)

Rule of thumb: if you copied a pattern from Stack Overflow, check which language the answer targeted. POSIX basic regex (BRE, plain grep) also requires escaping \(, \{, and \+ — the opposite of the syntax shown here.

Catastrophic backtracking: how a 20-character regex freezes the tab

JavaScript's engine (like PCRE and Python's re) is a backtracking matcher. When a pattern can match the same text in many overlapping ways, the engine tries every combination before declaring failure. With nested quantifiers the number of combinations grows exponentially — this is the bug class behind ReDoS (Regular-expression Denial of Service).

The classic trap is a quantifier inside a group that is itself quantified, where the inner parts can match the same characters:

Dangerous:   (a+)+$        on input "aaaaaaaaaaaaaaaaaaaaX"
             (.*a){20}     on a long string with no trailing "a"
             (a|a)*$       overlapping alternatives, same effect

Why it hangs: the engine can split 20 a's between the inner
"a+" and the outer "+" in 2^19 ways. Each split is retried
before the final "X" rejects the match.

Three reliable fixes (all valid in this JavaScript tester):

  • Make the inner class exclusive. Rewrite (a+)+ so the inner and outer parts cannot match the same characters. For real patterns, replace (.*,)* with ([^,]*,)* so each segment stops at the delimiter.
  • Anchor and bound. Add ^, $, and explicit length limits like {1,64} instead of open-ended + / * so the engine has fewer paths to explore.
  • Use the non-backtracking RE2 engine (Go's regexp, or the re2 library in Node/Python) for any regex that touches untrusted input. RE2 guarantees linear time but drops backreferences and lookaround.

If this tester ever appears to hang on a pattern, you have just reproduced a ReDoS locally — close the tab and rewrite the pattern. Never ship a backtracking regex that runs against user-supplied strings.

What each flag actually changes (g, i, m, s, u, y)

FlagNameEffect & common gotcha
gglobalFind all matches, not just the first. Advances lastIndex — reusing one RegExp object across calls can skip matches.
iignoreCaseCase-insensitive. Without u, only does simple ASCII folding — it will not match ß to SS.
mmultiline^ and $ match at every line break, not just string start/end. Does not change what . matches.
sdotAllLets . match newlines too. The flag people forget when a pattern works on one line but fails across lines.
uunicodeTreats the pattern as code points, enables \p{...} and \u{1F600}. Required to match emoji and astral characters correctly.
ystickyMatches only at exactly lastIndex — no scanning ahead. Used by tokenizers and lexers for predictable position tracking.

The newer d (hasIndices) and v(unicodeSets) flags also exist in modern browsers but are not part of this tester's toggle set.

Copy-paste regex reference (with honest caveats)

Every pattern below is written in JavaScript syntax and tested in this engine. Each comes with the caveat the copy-paste sites leave out — because no single regex is the "correct" one for most of these problems.

What it matchesPatternHonest caveat
Pragmatic email^[^\s@]+@[^\s@]+\.[^\s@]+$No regex fully validates email per RFC 5322. This rejects obvious garbage only — to truly verify, send a confirmation link.
Stricter email^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$Rejects valid quoted-local-parts and new TLDs over time. Stricter is not more correct — it just produces different false negatives.
HTTP/HTTPS URLhttps?:\/\/[^\s/$.?#].[^\s]*Matches the shape, not validity. Use the URL constructor for real parsing; regex is fine for find-in-text.
IPv4 address^(25[0-5]|2[0-4]\d|1?\d?\d)(\.(25[0-5]|2[0-4]\d|1?\d?\d)){3}$Correctly bounds each octet to 0-255, but accepts some short zero-padding like 01.02.03.04 and does not detect reserved ranges.
Hex color^#(?:[0-9a-fA-F]{3,4}|[0-9a-fA-F]{6}|[0-9a-fA-F]{8})$Covers 3, 4, 6, and 8-digit forms (with alpha). Does not validate rgb(), hsl(), or named colors.
ISO date (YYYY-MM-DD)^\d{4}-(0[1-9]|1[0-2])-(0[1-9]|[12]\d|3[01])$Bounds month and day but still accepts 2026-02-30. Calendar validity needs real date logic, not regex.
Time (24h, HH:MM)^([01]\d|2[0-3]):[0-5]\d$Add (:[0-5]\d)? for optional seconds. Does not handle 24:00 or leap seconds.
UUID v4^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$Enforces the version (4) and variant nibbles. Add the i flag for uppercase UUIDs.
US ZIP code^\d{5}(-\d{4})?$Format only. Many real 5-digit combinations are unassigned; only USPS data confirms a ZIP exists.
Slug (URL handle)^[a-z0-9]+(?:-[a-z0-9]+)*$Lowercase, hyphen-separated, no leading/trailing/double hyphens. Add Unicode handling separately for non-Latin slugs.
Strong password check^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[^A-Za-z0-9]).{12,}$Lookaheads require lower, upper, digit, symbol, 12+ chars. Length and a denylist beat complexity rules in practice (NIST guidance).
Semantic version^\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?(?:\+[0-9A-Za-z.-]+)?$Simplified SemVer 2.0.0. The official spec regex is longer and forbids leading zeros in pre-release identifiers.
Leading/trailing whitespace^\s+|\s+$Use with the g flag for trimming. In modern code String.prototype.trim() is faster and clearer.
Duplicate words\b(\w+)\s+\1\bUses a backreference (\1) to catch "the the". Backreferences are not available in RE2/Go.

Paste any pattern above into the tester and add your own sample text to confirm it does what you expect before shipping it.

Regex questions developers actually search for

Which regex flavor does this tester use?

JavaScript / ECMAScript, via the browser's native RegExp object. It supports lookahead, lookbehind, named groups, backreferences, and Unicode property escapes (with the u flag), but not PCRE-only features like atomic groups, possessive quantifiers, or inline modifiers such as (?i). If your pattern came from a PHP, Python, or grep context, expect small differences.

Why does my regex work in Python/PHP but not here?

The most common causes are inline flags ((?i), (?s)) which JavaScript does not support — use the flag toggles instead — and possessive quantifiers (a++) or atomic groups ((?>...)) which throw a syntax error in ECMAScript. Also, \d and \w are ASCII-only here unless you add the u flag and Unicode property escapes.

Is there a regex that fully validates an email address?

No. RFC 5322 allows quoted strings, comments, and folding whitespace that make a complete validating regex thousands of characters long and still imperfect. Use a pragmatic pattern like ^[^\s@]+@[^\s@]+\.[^\s@]+$ to reject obvious typos, then send a confirmation email — that is the only way to prove an address actually receives mail.

What is catastrophic backtracking and how do I avoid it?

It happens when a backtracking engine has to try exponentially many ways to match before failing, usually caused by nested quantifiers over overlapping characters like (a+)+ or (.*a)*. Avoid it by making inner character classes exclusive ([^,]* instead of .*), anchoring and bounding lengths, or running untrusted input through the linear-time RE2 engine. If a pattern hangs this tester, that is a ReDoS you just reproduced.

When do I need the s (dotAll) flag versus m (multiline)?

They control different things. m (multiline) changes ^ and $ so they match at every line break. s (dotAll) changes . so it matches newline characters too. If your pattern works on a single line but fails on multi-line input, you almost always want s, not m. You can enable both at once.

Why does my global regex skip every other match?

Reusing a single RegExp object with the g (or y) flag across calls to test() or exec() advances its lastIndex property between calls, so the next search starts mid-string. Either create a fresh RegExp each time, use String.matchAll(), or reset lastIndex = 0 before reusing it.