Cheatsheet
Regex syntax and patterns worth stealing
Tokens, classes, quantifiers, groups, lookaround, and flags, written in JavaScript regex syntax, which is close enough to PCRE that most of it carries over to Python, Go, and ripgrep. The last section is a set of tested patterns you can paste rather than reinvent.
Character classes and tokens
| Token | Matches |
|---|---|
| . | Any character except a line break, unless the s flag is set. |
| \d / \D | A digit 0 to 9, or anything that is not a digit. |
| \w / \W | A word character, meaning [A-Za-z0-9_] only. It is not Unicode aware. |
| \s / \S | Whitespace: space, tab, newline, carriage return, form feed, vertical tab, and Unicode spaces. |
| [abc] | Any one of a, b, or c. |
| [^abc] | Any single character that is not a, b, or c. Still consumes one character. |
| [a-z0-9_-] | Ranges plus literals. A hyphen first or last in the class is a literal hyphen. |
| \p{L} / \p{Lu} / \p{Nd} | Unicode property escapes: any letter, uppercase letter, decimal digit. Requires the u or v flag. |
| \P{L} | The negation of a Unicode property. |
| \t \n \r \f \v \0 | Tab, newline, carriage return, form feed, vertical tab, null. |
| \xA9 and \u{1F600} | Two digit hex escape, and a full code point escape which requires the u flag. A backslash plus a lowercase u plus four hex digits gives a single code unit. |
| \. \* \+ \? \( \[ \{ \| \\ \/ | Escaped metacharacters, matched literally. |
| [\p{L}--[a-z]] | Set difference, available only with the v flag. Also supports intersection with &&. |
Gotcha: \w and \d are ASCII only in JavaScript, so a name with an accent fails ^\w+$. Use \p{L} with the u flag when real names or non English input are possible.
Quantifiers, anchors, and boundaries
| Token | Matches |
|---|---|
| * | Zero or more, greedy. |
| + | One or more, greedy. |
| ? | Zero or one. Optional. |
| {3} / {2,} / {2,5} | Exactly three, two or more, and between two and five. |
| *? +? ?? {2,5}? | Lazy versions: match as few characters as possible. |
| ^ | Start of the string, or of each line when the m flag is set. |
| $ | End of the string, or of each line with the m flag. |
| \b / \B | Word boundary and non boundary. Zero width: it matches a position, not a character. |
| | | Alternation. It has the lowest precedence, so it splits the whole pattern unless grouped. |
| (?:...) | Non capturing group. Use this whenever you only need grouping. |
| (...) | Capturing group, numbered from 1 by the position of its opening bracket. |
| (?<year>\d{4}) | Named capture. Read it back from the groups object on the match. |
| \1 / \k<year> | Backreference: matches the exact text a group captured earlier. |
Gotcha: nesting quantifiers, as in (a+)+$, causes catastrophic backtracking: a few dozen characters can hang a process for minutes. Never run a user supplied pattern, and keep repetition inside a group bounded and anchored.
Lookaround
Lookarounds assert what surrounds a position without consuming characters, which is how you match a thing based on its context and still capture only the thing.
| Syntax | Asserts | Example |
|---|---|---|
| (?=...) | Positive lookahead: what follows must match. | \d+(?= dollars) |
| (?!...) | Negative lookahead: what follows must not match. | ^(?!admin)\w+$ |
| (?<=...) | Positive lookbehind: what precedes must match. | (?<=\$)\d+(?:\.\d{2})? |
| (?<!...) | Negative lookbehind: what precedes must not match. | (?<!\$)\b\d+\b |
| (?=.*x)(?=.*y) | Stacked lookaheads: several independent requirements at the same position. | ^(?=.*\d)(?=.*[A-Z]).{12,}$ |
| (?=(...)) | Capture inside a lookahead to find overlapping matches. | (?=(\d\d)) |
Gotcha: JavaScript allows variable length lookbehind, so (?<=\w+:) is legal. Most other engines, including Java, Python's built in re module, and Go's RE2, do not, and RE2 has no lookbehind at all. A pattern that works in the browser can fail in your log tooling.
Flags
| Flag | Effect |
|---|---|
| g | Global: find every match rather than stopping at the first. |
| i | Case insensitive. |
| m | Multiline: ^ and $ match at every line break, not just the ends of the string. |
| s | Dot all: the dot also matches newlines. |
| u | Unicode: enables \p{...} and correct handling of astral code points. |
| v | Unicode sets: a superset of u adding set operations and multi character string properties. |
| y | Sticky: matches only at lastIndex. The basis of hand written tokenizers. |
| d | Has indices: adds an indices array with the start and end offset of every group. |
Gotcha: a regex literal with the g or y flag is stateful. It keeps a lastIndex, so calling test() repeatedly on the same object alternates true and false. Either drop the g flag for tests, or build a fresh regex each time.
Using a regex in JavaScript
| Call | Returns |
|---|---|
| re.test(str) | A boolean. The cheapest check when you do not need the match itself. |
| str.match(re) | Without g, a match object with groups. With g, a flat array of matched strings and no groups. |
| str.matchAll(re) | An iterator of full match objects. Requires the g flag; this is what you usually want. |
| re.exec(str) | One match object per call, advancing lastIndex when the g flag is set. |
| str.replace(re, 'x') | First match replaced, or all of them when the g flag is set. |
| str.replaceAll(re, 'x') | Same, but throws if the regex lacks the g flag. Explicit and safer. |
| str.replace(re, (m, p1) => ...) | Function replacer: match, each capture, the offset, then the whole string. |
| '$1 $<name> $& $$' | Replacement tokens: numbered group, named group, whole match, a literal dollar sign. |
| str.split(re) | Splits on the pattern. Capturing groups are included in the output array. |
| str.search(re) | The index of the first match, or -1. |
| new RegExp(src, 'gi') | Build from a string. Every backslash must be doubled in the source string. |
| RegExp.escape(userInput) | Escapes metacharacters so user text is treated literally. Node 22+, Chrome 136+, Firefox 134+, Safari 18.2+. |
const LOG = /^(?<ts>\S+)\s+(?<level>[A-Z]+)\s+(?<msg>.*)$/;
for (const line of lines) {
const m = LOG.exec(line);
if (!m?.groups) continue;
const { ts, level, msg } = m.groups;
// ...
}
// Reformat dates with named groups in the replacement string.
'2026-08-07'.replace(
/(?<y>\d{4})-(?<m>\d{2})-(?<d>\d{2})/,
'$<m>/$<d>/$<y>'
); // "08/07/2026" Patterns worth stealing
| Goal | Pattern |
|---|---|
| Pragmatic email check | /^[^\s@]+@[^\s@]+\.[^\s@]+$/ |
| HTTP or HTTPS URL | /^https?:\/\/[^\s\/$.?#].\S*$/i |
| ISO date, YYYY-MM-DD | /^\d{4}-\d{2}-\d{2}$/ |
| 24 hour time | /^([01]\d|2[0-3]):[0-5]\d$/ |
| Hex color, 3 or 6 digits | /^#(?:[0-9a-f]{3}|[0-9a-f]{6})$/i |
| URL slug | /^[a-z0-9]+(?:-[a-z0-9]+)*$/ |
| UUID version 4 | /^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i |
| IPv4 address | /^(?:(?: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)$/ |
| Semantic version | /^\d+\.\d+\.\d+(?:-[\w.]+)?(?:\+[\w.]+)?$/ |
| Password: 12 or more with a digit, lower, and upper | /^(?=.*\d)(?=.*[a-z])(?=.*[A-Z]).{12,}$/ |
| Doubled word, such as "the the" | /\b(\w+)\s+\1\b/gi |
| Trailing whitespace on any line | /[ \t]+$/gm |
| Collapse runs of whitespace | /\s+/g |
| Quoted string, escapes allowed | /"(?:[^"\\]|\\.)*"/ |
| Leading or trailing slash | /^\/+|\/+$/g |
Gotcha: the email pattern above is deliberately loose. A fully RFC 5322 compliant expression runs to hundreds of characters, rejects addresses that work, and still cannot tell you whether the mailbox exists. Check the shape, then send a confirmation message. Equally, do not parse HTML or JSON with regex; use a parser.
Keep going
Regex shows up everywhere else on this site: git log -G in the Git cheatsheet, the Postgres ~ operator in the SQL cheatsheet, and arbitrary variant selectors in the Tailwind cheatsheet.
The cheatsheet index lists every quick reference, and the testing tool directory covers the runners you should be pinning these patterns down with.