A regex tester shows where a pattern matches your text, right away
A regular expression (regex) is a rule that describes the shape of the text you want to find. \d{4} means "four digits" and [A-Z][a-z]+ means "a capital letter followed by lowercase letters". The rule is short, but changing a single symbol can change what it finds by a lot, so it pays to try a pattern on real text before you put it in code.
This tool runs your pattern with the JavaScript regex engine of the browser you are using. Matches are highlighted in the text, capture groups are color-coded and become columns in the match table, and the pattern is broken into parts with a plain-language explanation of each one. That also makes it a handy way to read a regex someone else wrote.
Why test a regex before you put it in code
A regex goes wrong in two ways. It misses something it should find (a phone number check rejects 5551234567 because it expects dashes), or it accepts something it should not (a date pattern lets 2026-13-45 through). Neither is a syntax error, so the browser gives no warning. The surest check is to try examples that should match together with examples that should not.
The third problem is speed. On July 2, 2019, Cloudflare deployed a new regex in a web application firewall (WAF) rule. Backtracking exploded, HTTP-serving CPUs ran close to 100%, and the service was down for 27 minutes. According to the company's public post-mortem, the culprit was a small piece of a long pattern: .*(?:.*=.*). Two patterns that find the same text can differ in the number of steps by a factor of hundreds or more, so test with text of a realistic length.
Testing a regex first helps in situations like these.
- Input validation: when you decide the phone, email or password rules for a sign-up form, put examples that must pass and examples that must be blocked on separate lines.
- Extracting values: when you pull dates, order numbers or IP addresses out of logs, CSV files or a copied table, check that the group values split correctly. The match table can be pasted straight into a spreadsheet.
- Bulk replace: before you run find and replace in an editor or in code, look at which spots change and what the result looks like.
- Reading someone else's regex: see what a pattern in old code actually looks for, one part at a time, and check whether it has a shape that can get slow.
How to use it
- Type the regex in the Pattern field, without the surrounding slashes. If you paste a whole literal such as
/abc/gi, the tool splits it into the pattern and its flags. - Turn flags on with the letter buttons next to it. Turn on
gto find every match. - Paste your text into Test text. Matches are highlighted at once, and the status line above shows how many there are.
- For a validation pattern (
^…$), turn on Test each line and put one example per line. Lines that pass get a ✓ and lines that fail get a ✗. - In the Match table, read the group values; pressing a row number makes that match flash in the text. In the Replace tab, type a replacement and see the result. The Code tab gives you JavaScript to take with you.
How to read the pattern breakdown
The breakdown splits the pattern into the units the engine reads. Each cell shows the original symbols on top and their meaning below. A quantifier (+, {4} and so on) is attached to the cell before it, so you read it together, as in "digit 0–9, 4 times". The colored bar under a cell is the capture group the cell belongs to, and the same color is used for the group underline in the text and for the column header in the match table. Dashed cells are non-capturing groups, lookaheads, lookbehinds and "or".
| Cell | Meaning | Group color |
|---|---|---|
(?<y> | Start of group y (group 1) | Color 1 |
\d{4} | Digit 0–9, 4 times | Color 1 |
) | End of group y | Color 1 |
- | A hyphen, as written | None |
(?<m> \d{2} ) | Group m: digit, 2 times | Color 2 |
An orange-bordered cell marks a shape that can make matching very slow: a repeat inside a repeat such as (a+)+, or the same repeat twice in a row such as .*.*. A red cell points at the spot of a syntax error when the browser rejects the pattern.
Common syntax
The "Finds" column below was produced by running each example text through the same JavaScript engine this tool uses.
| Symbol | Meaning | Example pattern | Example text | Finds |
|---|---|---|---|---|
\d | One digit, 0–9 | \d+ | Flat 102B | 102 |
\w | One letter (A–Z, a–z), digit or underscore | \w+ | user_01 here | user_01, here |
. | Any character except a line break | a.c | abc a-c a↵c | abc, a-c |
[ ] | One of the characters inside, - makes a range | [A-Z][a-z]+ | Roberin Tools | Roberin, Tools |
[^ ] | One character that is not inside | [^0-9]+ | 555-1234 | - |
^ $ | Start and end of the text (or line) | ^\d+$ | 2026 | 2026 |
{n,m} | The previous cell, n to m times | \d{2,3} | 1 22 333 4444 | 22, 333, 444 |
| | Or | cat|dog | hotdog cat | dog, cat |
\b | Word boundary (A–Z, a–z, digits, _) | \bcat\b | cat category | cat |
(?= ) | Lookahead: this must follow (those characters are not part of the match) | \d+(?= USD) | 3 pcs 5000 USD | 5000 |
(?<= ) | Lookbehind: this must come before | (?<=\$)\d+ | $30 and 40 | 30 |
\1 | The text captured by group 1, once more | (\w)\1 | book keeper | oo, ee |
+? *? | Repeat as few times as possible | <.+?> | <b>x</b> | <b>, </b> |
What the 8 flags change
A flag is a setting that applies to the whole pattern. The same pattern on the same text can give different results depending on the flags.
| Flag | What it does | Example pattern · text | Off | On |
|---|---|---|---|---|
g | Keeps going after the first match and finds all of them | a · a a a | a | a, a, a |
i | Treats upper and lower case as the same | abc · ABC | none | ABC |
m | ^ and $ match at every line | ^\d · 1↵2 | 1 | 1, 2 |
s | . also matches a line break | a.b · a↵b | none | a↵b |
u | Reads emoji and similar characters as one character and enables \p{…} | . · 😀 | 2 halves | 😀 |
v | Adds set subtraction (--) and intersection (&&) to u | [\p{L}--[a-z]] · aZé | syntax error | Z, é |
y | Matches only if it continues right where the last match ended | \d · 12a3 (with g) | 1, 2, 3 | 1, 2 |
d | Also returns the start and end of each group (indices) | The matches are the same. This tool computes positions whatever the flags and uses them in the match table. | ||
u and v cannot be on together. When you turn one on, the tool turns the other off and says so in the status line. Newer syntax such as the v flag or modifiers like (?i:…) can cause a syntax error in an older browser.
Capture groups, named groups and backreferences
Anything wrapped in parentheses ( ) becomes a capture group, which is stored separately inside the match. Groups are numbered 1, 2, 3 in the order their opening parentheses appear, and a group such as (?<y>…) has both a number and a name. If you only need to group something without storing it, use (?:…). A non-capturing group takes no number, so the numbers of later groups do not shift.
To look for the text an earlier group captured, use a backreference: \1 or \k<name>. (\w)\1 finds the same character twice in a row. Remember also that when a group repeats ((\d)+), only the value from the last repetition is kept. In the match table a "—" means the group did not take part in that match (group 1 in (a)|(b) when b matches), and "empty" means the group matched an empty string.
Symbols you can use in the replacement
The Replace tab follows the same rules as JavaScript's String.prototype.replace. The table below uses the text "Order 2026-10-22 shipped" with the pattern (\d{4})-(\d{2})-(\d{2}).
| Symbol | Meaning | Replacement | Result |
|---|---|---|---|
$1–$99 | Numbered group | $3.$2.$1 | Order 22.10.2026 shipped |
$<name> | Named group (when the pattern has names) | $<m>/$<d> | Order 10/22 shipped |
$& | The whole match | [$&] | Order [2026-10-22] shipped |
$` | All the text before the match | ($`) | Order (Order ) shipped |
$' | All the text after the match | ($') | Order ( shipped) shipped |
$$ | One literal $ | $$1 | Order $1 shipped |
\n | Not a line break: the characters \ and n | $1\n$2 | Order 2026\n10 shipped |
A number larger than the number of groups ($7) stays as literal text, and so does $0, which is not a group. To put in a line break, press Enter in the replacement field. In code, use "\n" inside the string.
Accents and non-English letters
In JavaScript, \w and \b treat only A–Z, a–z, digits and the underscore as "word characters". That stays true when the u flag is on. So \w+ splits café into caf, and \bcafé\b cannot find the word at all, because there is no boundary between é and a space. [A-Za-z] has the same limit. The shortcut that covers every letter of every script is the Unicode property \p{L}, which needs the u flag.
| Pattern | Finds | Notes |
|---|---|---|
\w+ | caf, Zo, abc, 2026 | Stops at é and ë, and includes digits |
[A-Za-z]+ | caf, Zo, abc | Plain English letters only |
[A-Za-zÀ-ÿ]+ | café, Zoë, abc | Adds the Latin-1 accented range; not letters beyond it |
\p{L}+ (u) | café, Zoë, abc, 日本 | Letters of any script |
\bcafé\b | none | \b does not treat é as a word character |
(?<![\p{L}\d_])café(?![\p{L}\d_]) (u) | café | Lookarounds make the boundary yourself |
The range À-ÿ is U+00C0 to U+00FF, so it also contains the multiplication sign × and the division sign ÷. It is also worth knowing that a letter can be stored in two ways: as one character (é) or as e plus a combining accent. \p{L}+ stops at the combining accent because that mark is not a letter; use [\p{L}\p{M}]+ if your text may contain the second form. For word boundaries around accented words, write them with lookarounds, as in the last row of the table (with the u flag).
Test each line versus the m flag
The m flag checks the whole text at once and makes ^ and $ match at the start and end of every line. A match can therefore run across lines (\s also accepts a line break), and the result is only a count of matches. Test each line cuts the text into lines and checks every line as its own text. A match never crosses a line, every line gets a pass (✓) or a fail (✗), and the status line reads something like "3 of 6 lines pass". When you are trying a pattern that must validate one whole input value, as in a sign-up form, testing each line is the same thing that happens in real use. Replace works line by line too, and the lines are joined again afterward.
Slow patterns and the 1-second limit
The JavaScript regex engine looks for a way to match, and when a choice fails it backs up and tries another path (backtracking). A pattern in which several repeats can share the same characters tries every way of sharing them when the text does not match. Describing the problem part of its pattern in simplified form as .*.*=.*, the Cloudflare report said that x=x matches in 23 steps, a text with 20 x characters after x= needs 555 steps to match, and 20 x characters with no = take 4,067 steps to fail. Adding a single ; at the end, .*.*=.*;, makes the text with 20 x characters after x= fail only after 5,353 steps. With a repeat inside a repeat, such as (a+)+$, the number of attempts roughly doubles for every extra character.
This tool runs the pattern in a separate worker (a Web Worker) and, if it has not finished within 1 second, ends the job and tells you. The page does not freeze. The orange cells in the breakdown are the most likely cause. There are usually three ways to fix it.
- Remove the repeat inside the repeat. Instead of
(\w+\s?)+$, write\w+(?:\s\w+)*$, so that every repetition has to include a different character (here a space). - Use a set that excludes the stopping character instead of
.*. Changing.*.*=.*to[^=]*=.*finds the same text with almost nothing to back up over. - Merge alternatives that can split the same text in several ways.
(\d|\d\d)+$tries every way of cutting a run of digits into one- and two-digit pieces, while\d+$finishes in one pass.
How flavors differ
Regex syntax differs a little from language to language. The results of this tool follow JavaScript, so check the differences below when you move a pattern elsewhere. The table was produced by running the examples in Node.js 26, Python 3.14 and PHP 8.5 (PCRE2 10.44); Java was checked against the official Java 21 documentation.
| Item | JavaScript | Python re | Java | PHP preg_* |
|---|---|---|---|---|
| Named group | (?<n>…) | Only (?P<n>…); (?<n>…) is an error | (?<n>…) | (?<n>…), (?P<n>…) |
| Group in the replacement | $1, $<n> | \1, \g<n> | $1, ${n} | $1, \1, ${1} (names do not work) |
Does \w match non-English letters? | No (even with u) | Yes (default for string patterns; no with re.ASCII) | No (yes with UNICODE_CHARACTER_CLASS) | No (yes with /u) |
Possessive quantifier a++, atomic group (?>…) | None (syntax error) | Yes (since 3.11) | Yes | Yes |
| Where flags go | Letters after /…/gi | Arguments such as re.I; find all with findall or sub | Arguments such as Pattern.CASE_INSENSITIVE | Letters after the delimiter /…/iu |
The length allowed inside a lookbehind ((?<=…)) also differs. JavaScript accepts a pattern of unbounded length such as (?<=a+). Python accepts only a fixed length. PHP, from PCRE2 10.43, accepts patterns with a length limit ((?<=a{1,3}), default maximum 255 characters). Python's \d also treats the full-width digits "12" as digits, while JavaScript's \d sees only 0–9.
Common mistakes
- One backslash when building from a string: the JavaScript string
"\d+"becomesd+. You have to write it twice, as innew RegExp("\\d+"). The "Built from a string" line in the Code tab shows the pattern with this already done. - An unescaped period:
1.5also matches "125". Write a literal period as1\.5. - Calling
test()several times on a regex with the g flag: the regex remembers its last position (lastIndex), so results for the same text alternate true, false, true. If you only need a yes-or-no check, it is safer to leave g off. - The greedy
.*:<.+>takes all of "<b>x</b>" in one go. To take one tag at a time, use<.+?>or<[^>]+>. - A validation pattern on multi-line text: without the m flag,
^…$treats the whole text as one piece, so several lines give 0 matches. To try several examples, turn on Test each line. - Trying to validate an email perfectly with one regex: the addresses the standard allows are so varied that a simple pattern either misses some or accepts extra ones. The email pattern in the examples is a rough filter; sending a confirmation email is the usual way to be sure.
Limits
- Results follow the JavaScript regex engine of the browser you are using. Patterns in Python, Java, PHP and editors can differ in syntax and results, as the table above shows.
- The limits are 1,000 characters for the pattern, 50,000 for the test text, 2,000 for the replacement and 200,000 for the result. The match table and the highlights show the first 1,000 matches; the match count and the replaced result include all of them.
- A run stops after 1 second. On a slow device the same pattern can take longer.
- The "may be slow" mark in the breakdown only looks for common shapes. A pattern without the mark is not guaranteed to be fast on every text.
- "Line:col" in the match table counts JavaScript string positions (UTF-16), so one emoji takes two columns.
- The pattern and text you enter are processed only inside your browser and are not sent to a server. They are not kept in the address or in storage either, so a refresh brings back the first example.
Basis: the syntax and replacement rules were checked against the ECMAScript specification and MDN, and the other languages against the official Python, Java and PHP documentation and the PCRE2 change log, on October 22, 2026. The examples in the tables were run on Node.js 26 (V8), Python 3.14 and PHP 8.5 with the same results.
Sources
- 01ECMAScript: RegExp (Regular Expression) ObjectsJavaScript regex syntax and execution rules
- 02ECMAScript: GetSubstitutionHow $1, $<name>, $&, $`, $' and $$ are handled
- 03MDN: Regular expressionsFlags, modifiers (?i:…), the v flag, named groups
- 04MDN: String.prototype.replace()Special symbols in the replacement string
- 05MDN: Character class escapeThe characters \d, \w and \s accept
- 06Cloudflare: Details of the Cloudflare outage on July 2, 201927-minute outage from WAF regex backtracking, step counts for .*.*=.*
- 07Python: re — Regular expression operations(?P<name>…), \g<name>, fixed-length lookbehind, possessive quantifiers in 3.11
- 08Java 21: Pattern\w = [a-zA-Z_0-9] without UNICODE_CHARACTER_CLASS, possessive quantifiers
- 09PHP: preg_replace$n, \n and ${n} in the replacement
- 10PCRE2 ChangeLog10.43: variable-length lookbehind with a length limit (default 255)
- 11Unicode: Latin-1 Supplement (0080–00FF)Accented Latin letters in the À–ÿ range
Frequently asked questions
Can I paste the slashes and flags along with the pattern?
The pattern field takes only what goes between the slashes, and flags are turned on with the letter buttons on the right. If you paste a whole literal such as /abc/gi, the tool splits it into the pattern abc and the flags g and i. If you type a slash by hand, it is read as a character to find.
Why is only one place highlighted when several places match?
With the g flag off, matching stops at the first match. The status line then says how many matches there would be with g on, so press the g button to find them all.
Why does \w not match accented letters like é or ñ?
In JavaScript, \w accepts only A–Z, a–z, digits and the underscore, even with the u flag on. For accented Latin letters you can add a range such as [A-Za-zÀ-ÿ], and for letters of every script use \p{L} with the u flag.
How is the m flag different from Test each line?
The m flag checks the whole text at once and lets ^ and $ match at every line. Test each line checks every line as its own text and marks each one as pass or fail. To try a pattern that validates one input value on several examples, Test each line is the right choice.
How do I put group values into the replacement?
Use $1, $2 for numbered groups, $<name> for named groups and $& for the whole match. The token buttons in the Replace tab insert them at the cursor. \n is inserted as the two characters, not as a line break, so press Enter to start a new line.
If the 1-second limit stops a run, is my pattern wrong?
The syntax can be correct and the pattern still cause backtracking to explode. A repeat inside a repeat such as (a+)+, or the same repeat twice in a row such as .*.*, makes the attempts grow quickly on long text that does not match. Check the orange cells in the breakdown, merge the repeats, or use a narrow set like [^=]* instead of .*.
Will I get the same results in Python, Java or PHP?
The basics are alike, but there are differences. Python writes named groups as (?P<name>…) and its \w also accepts non-English letters, and in PHP \w accepts them when you add /u. Group references in the replacement also differ, such as \1 or ${name}. See the "How flavors differ" table in the article.
Is the text I enter sent to a server?
No. The pattern and text run only in a Web Worker inside your browser and are not kept in the address or in storage. A refresh brings back the first example.