Regex Cheat Sheet
Patterns for finding, checking and replacing text: syntax, flags, ready-made patterns (email, URL, dates, Belgian phone/IBAN…) and how regex works in JavaScript, Python, PHP, C#, grep, sed and VS Code. Test your patterns at regex101.com.
On this page
Characters & Classes #
| Pattern | Matches |
|---|---|
abc | The literal text "abc" |
. | Any character except a newline (with the s flag: also newlines) |
\d / \D | A digit / not a digit |
\w / \W | A word character (letter, digit, _) / not one |
\s / \S | Whitespace (space, tab, newline) / not whitespace |
[abc] | One of a, b or c |
[^abc] | Any character except a, b, c |
[a-z] / [A-Za-z0-9] | A range / several ranges |
[-.] / [a\-z] | A literal -: put it first or last, or escape it |
\. \* \+ \? \( \[ \\ | A literal special character: escape it with \ |
\t \n \r | Tab, newline, carriage return |
\p{L} / \p{Lu} / \p{N} | Any letter / uppercase letter / number in any language (JS needs the u flag; not in Python's re) |
In JavaScript (without u) \w and \d are ASCII only: \w doesn't match é. In Python 3 they match any Unicode letter or digit; add re.ASCII to limit them.
Anchors & Boundaries #
| Pattern | Matches |
|---|---|
^ | Start of the text (start of every line with the m flag) |
$ | End of the text (end of every line with m) |
\b | Word boundary: \bcat\b finds "cat" but not "concat" |
\B | Not a word boundary: \Bcat finds the "cat" in "concat" |
\A / \z | Very start / very end of the text, ignores m (Python: \A / \Z; not in JavaScript) |
For validation always anchor both ends: ^\d+$. Without anchors \d+ also "matches" abc123xyz.
Quantifiers #
| Pattern | Meaning |
|---|---|
a* | 0 or more |
a+ | 1 or more |
a? | 0 or 1 (optional) |
a{3} | Exactly 3 |
a{2,} | 2 or more |
a{2,5} | 2 to 5 |
*? / +? / ?? / {2,5}? | Lazy: as few as possible |
a*+ / (?>a*) | Possessive / atomic: never gives characters back (PHP, Java, .NET, Python 3.11+; not JavaScript) |
Greedy vs lazy: on <a><b>, <.+> matches everything, <.+?> matches just <a>.
Groups & Alternation #
| Pattern | Meaning |
|---|---|
(abc) | Capturing group: group 1, 2… in the result, $1 / \1 in replacements |
(?:abc) | Group without capturing (for | or quantifiers only) |
(?<year>\d{4}) | Named group (JS, PHP, .NET; Python writes (?P<year>...)) |
cat|dog | cat or dog |
(cat|dog)s? | cat, cats, dog, dogs |
(\w)\1 | Back-reference: the same text as group 1 again (ll in "hello") |
\k<year> | Back-reference to a named group (Python: (?P=year)) |
Lookarounds #
Check what comes before or after without including it in the match.
| Pattern | Meaning |
|---|---|
x(?=y) | x, only if followed by y |
x(?!y) | x, only if not followed by y |
(?<=y)x | x, only if preceded by y |
(?<!y)x | x, only if not preceded by y |
\d+(?=€) | The number in "25€", without the € |
(?<=€)\d+ | The number in "€25", without the € |
^(?=.*\d)(?=.*[A-Z]).{8,}$ | Several conditions at once (here: a digit and a capital, 8+ characters) |
Python's re and PHP need fixed-width lookbehind: (?<=\d+) fails, (?<=\d{2}) works. JavaScript and .NET accept any width.
Flags #
| Flag | Effect | JavaScript | Python | PHP | C# |
|---|---|---|---|---|---|
| Case-insensitive | a also matches A | /x/i | re.I | /x/i | RegexOptions.IgnoreCase |
| Multiline | ^ $ match at every line | m | re.M | m | Multiline |
| Dot-all | . also matches newlines | s | re.S | s | Singleline |
| Global | Find all matches, not just the first | g | findall / finditer | preg_match_all | Matches() |
| Unicode | Full Unicode, \p{...} | u (or v, ES2024) | default | u | default |
| Verbose | Allow spaces and # comments in the pattern | not available | re.X | x | IgnorePatternWhitespace |
| Inline | Flag inside the pattern: (?i)abc, (?i:ab)c | (?i:...) only (ES2025, recent browsers) | (?i) | (?i) | (?i) |
Replacing #
| Syntax | Inserts | Where |
|---|---|---|
$1 / $2 | Group 1 / 2 | JavaScript, PHP, C#, VS Code |
\1 / \2 | Group 1 / 2 | Python, sed, grep/Perl-style tools |
$<year> | Named group | JavaScript |
\g<year> | Named group | Python |
${year} | Named group | C# |
${1}0 | Group 1 followed by a literal 0 (not group 10) | PHP, C# |
$& / $0 | The whole match | $&: JavaScript, C#; $0: PHP, C#; Python: \g<0> |
$$ | A literal $ | JavaScript, C# |
\u$1 / \U$1 / \L$1 | Group 1 with first letter uppercase / all uppercase / all lowercase | VS Code (replace box) |
"2026-10-12".replace(/(?<y>\d{4})-(?<m>\d\d)-(?<d>\d\d)/, "$<d>/$<m>/$<y>"); // "12/10/2026"
"hello world".replace(/\b\w/g, (c) => c.toUpperCase()); // "Hello World"
Ready-Made Patterns #
Tested with JavaScript and Python. They check the format, not whether something really exists (use a library or send a test email for that). In a JavaScript literal /.../, escape / as \/.
| Purpose | Pattern |
|---|---|
| Whole number | ^-?\d+$ |
Decimal number (. or ,) | ^-?\d+(?:[.,]\d+)?$ |
| Email address (practical check) | ^[\w.+-]+@[\w-]+(?:\.[\w-]+)*\.[A-Za-z]{2,}$ |
URL (http/https) (flag i) | ^https?://[^\s/$.?#].[^\s]*$ |
Domain name (flag i) | ^(?=.{1,253}$)(?:[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?\.)+[a-z]{2,63}$ |
| IPv4 address (0–255 per part) | ^(?:(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)\.){3}(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)$ |
IPv6 address (full form only) (flag i) | ^(?:[0-9a-f]{1,4}:){7}[0-9a-f]{1,4}$ |
Date YYYY-MM-DD | ^\d{4}-(?:0[1-9]|1[0-2])-(?:0[1-9]|[12]\d|3[01])$ |
Date DD/MM/YYYY | ^(?:0[1-9]|[12]\d|3[01])/(?:0[1-9]|1[0-2])/\d{4}$ |
Time HH:MM or HH:MM:SS (24 h) | ^(?:[01]\d|2[0-3]):[0-5]\d(?::[0-5]\d)?$ |
Hex color #fff / #ffffff (flag i) | ^#(?:[0-9a-f]{3}){1,2}$ |
UUID (flag i) | ^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$ |
URL slug (git-bash) | ^[a-z0-9]+(?:-[a-z0-9]+)*$ |
| Username, 3–16 letters/digits/_ | ^[a-zA-Z0-9_]{3,16}$ |
| Password: 8+ chars, lower, upper, digit, symbol | ^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[^\w\s]).{8,}$ |
MAC address (flag i) | ^(?:[0-9a-f]{2}[:-]){5}[0-9a-f]{2}$ |
| Version number (SemVer) | ^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(?:-[\w.-]+)?(?:\+[\w.-]+)?$ |
| Belgian postal code | ^[1-9]\d{3}$ |
| Belgian phone number | ^(?:\+32|0032|0)\s?[1-9](?:[\s./-]?\d){7,8}$ |
| Belgian IBAN | ^BE\d{2}(?:\s?\d{4}){3}$ |
| Belgian VAT / company number | ^BE\s?[01]\d{3}\.?\d{3}\.?\d{3}$ |
HTML tag (for quick searches, not parsing) (flag i) | </?[a-z][a-z0-9-]*\b[^>]*> |
Markdown link [text](url) | \[([^\]]+)\]\(([^)\s]+)\) |
Image file extension (flag i) | \.(jpe?g|png|gif|webp)$ |
Trailing whitespace (flag m) | [ \t]+$ |
Repeated word (the the) (flag i) | \b(\w+)\s+\1\b |
| YouTube video id (group 1) | (?:youtube\.com/watch\?v=|youtu\.be/)([\w-]{11}) |
Differences Between Languages #
| Feature | JavaScript | Python re | PHP (PCRE) | C# (.NET) | grep -E |
|---|---|---|---|---|---|
| Named group | (?<n>...) | (?P<n>...) | (?<n>...) | (?<n>...) | no |
| Lookbehind | any width | fixed width | fixed width | any width | no (use grep -P) |
\d \w \s | yes (ASCII) | yes (Unicode) | yes | yes (Unicode) | \w \s yes (GNU), \d no: use [0-9] |
\p{L} | with u | no (regex package: yes) | with u | yes | no |
| Possessive / atomic | no | yes (3.11+) | yes | atomic (?>...) only | no |
Inline flags (?i) | (?i:...) (ES2025) | yes | yes | yes | no (grep -i) |
| Pattern written as | /x/g or new RegExp("x", "g") | r"x" (raw string) | "/x/i" (with delimiters) | @"x" (verbatim string) | 'x' |
JavaScript #
| Code | What it does |
|---|---|
/^\d+$/.test(s) | Does it match? (true / false) |
s.match(/\d+/) | First match with groups, or null |
s.match(/\d+/g) | Array of all matches (no groups), or null |
[...s.matchAll(/(\w)(\d)/g)] | All matches with groups (needs g) |
m.groups.year | A named group from match / exec |
s.replace(/a/g, "b") / s.replaceAll(/a/g, "b") | Replace all (replace without g only replaces the first) |
s.split(/\s*,\s*/) | Split on a pattern: "a , b,c" gives ["a", "b", "c"] |
s.search(/\d/) | Index of the first match, or -1 |
new RegExp(`^${word}$`, "i") | Build a pattern from a variable (escape user input first!) |
RegExp.escape(text) | Escape special characters in user input (ES2025; recent browsers, Node 24+) |
A /g regex remembers lastIndex: calling re.test() twice on the same string can give true then false. Use a new regex or drop g for tests.
Python #
| Code | What it does |
|---|---|
import re | The built-in module |
re.search(r"\d+", s) | First match anywhere, or None |
re.match(r"\d+", s) / re.fullmatch(r"\d+", s) | Match at the start only / the whole string must match |
m.group() / m.group(1) / m.group("year") | Whole match / group 1 / named group |
re.findall(r"\d+", s) | List of all matches (strings, or tuples if there are groups) |
re.finditer(r"\d+", s) | Iterator of match objects (with positions) |
re.sub(r"(\w+) (\w+)", r"\2 \1", s) | Replace ("hello world" gives "world hello") |
re.sub(r"\d", lambda m: m[0] * 2, s) | Replace with a function |
re.split(r"\s*,\s*", s) | Split on a pattern |
pattern = re.compile(r"\d+", re.I) | Compile once, reuse many times |
re.escape(text) | Escape user input: "1.5+2" gives 1\.5\+2 |
Always use raw strings r"...": in a normal string "\b" is a backspace, not a word boundary.
PHP #
| Code | What it does |
|---|---|
preg_match('/^\d+$/', $s) | 1 if it matches, 0 if not (false on a pattern error) |
preg_match('/(?<y>\d{4})/', $s, $m) | Groups end up in $m ($m[1], $m['y']) |
preg_match_all('/\d+/', $s, $m) | All matches: $m[0] is the list; returns the count |
preg_replace('/(\w+) (\w+)/', '$2 $1', $s) | Replace (single quotes, so PHP doesn't read $2 as a variable) |
preg_replace_callback('/\b\w/', fn($m) => strtoupper($m[0]), $s) | Replace with a function |
preg_split('/\s*,\s*/', $s) | Split on a pattern |
preg_quote($text, '/') | Escape user input (pass your delimiter) |
'/é/u' | u flag: needed for UTF-8 text and \p{L} |
The pattern is a string with delimiters: '/x/i', '#https?://#' (use # or ~ when the pattern contains /).
C# (.NET) #
| Code | What it does |
|---|---|
using System.Text.RegularExpressions; | The namespace |
Regex.IsMatch(s, @"^\d+$") | Does it match? |
Regex.Match(s, @"(?<y>\d{4})").Groups["y"].Value | A named group from the first match |
foreach (Match m in Regex.Matches(s, @"\d+")) | Loop over all matches |
Regex.Replace(s, @"(\w+) (\w+)", "$2 $1") | Replace |
Regex.Split(s, @"\s*,\s*") | Split on a pattern |
Regex.Escape(text) | Escape user input |
new Regex(pattern, RegexOptions.IgnoreCase, TimeSpan.FromSeconds(1)) | With options and a timeout against slow patterns |
[GeneratedRegex(@"\d+")] private static partial Regex Digits(); | Pattern compiled at build time (.NET 7+, fastest) |
Use verbatim strings @"..." so backslashes stay single.
Command Line & Editors #
| Tool | How |
|---|---|
grep -E 'colou?r' file | Extended regex (+, ?, |, () without backslashes) |
grep -P '\d+(?=€)' file | Perl-style: \d, lookarounds (GNU grep) |
grep -oE '[0-9]+' file | Print only the matching part |
grep -rniE 'todo|fixme' src/ | Recursive, case-insensitive, with line numbers |
sed -E 's/(\w+) (\w+)/\2 \1/' file | Replace (add -i to edit the file in place, g after the last / for all matches) |
rg 'pattern' | ripgrep: fast recursive search, Rust regex syntax (no lookarounds unless -P) |
VS Code Ctrl + F / Ctrl + H | Click .* (or Alt + R) to use regex; $1 in the replace box |
VS Code Ctrl + Shift + F | Regex search across all files |
Performance & Pitfalls #
| Problem | Fix |
|---|---|
(a+)+$ or (.*)* on a long non-matching text hangs | Nested quantifiers backtrack exponentially ("ReDoS"): rewrite without nesting, use atomic/possessive groups or a timeout |
| User input inside a pattern breaks or becomes a security hole | Escape it first (re.escape, preg_quote, Regex.Escape, RegExp.escape) |
.* grabs too much | Use a lazy .*? or a negated class like [^"]* (faster and clearer) |
| Validation accepts garbage | Anchor with ^...$ (or fullmatch in Python) |
. doesn't match across lines | Add the s flag, or use [\s\S] in older JavaScript |
| Parsing HTML, JSON or email addresses perfectly | Use a real parser or library; regex is fine for quick searches |
Tips #
- Build and test patterns on regex101.com: pick the right flavor (PCRE, Python, ECMAScript, .NET) and it explains every part.
- Start simple and add cases one by one; write a few should-match and should-not-match examples first.
- Prefer specific classes over
.:[^,]+says "up to the next comma" and avoids surprises. - Use non-capturing
(?:...)when you only need grouping, and named groups when you have more than two. - Comment long patterns: Python
re.X, PHP and .NETxlet you split a pattern over lines with comments. - Regex can't count nesting (balanced brackets) or validate dates fully (
2026-02-31passes the date pattern): check those in code.
Nothing on this sheet matches “”.