Regex Identifier #
This article is a complete reference of all identifiers — symbols, metacharacters, and syntax constructs — that can be used in regular expressions in TypeScript. Unlike the previous Regex article, which discussed how to use regex and its methods, this article focuses on the dictionary of the elements that make up a regex pattern itself: what every symbol means, how they interact, and which traps to watch out for. Use this article as a reference when building or reading complex regex patterns.
Literal Characters #
Literal characters are characters that match themselves exactly. The majority of alphanumeric characters are literals:
// Letters and numbers are literals — they match themselves
/abc/.test("abc"); // true — exact match
/abc/.test("ABC"); // false — case-sensitive by default
/abc/.test("xabcx"); // true — found inside the string
// NOTE: Some characters have special meaning (metacharacters)
// and must be escaped with \ to be treated as literals
// . * + ? ^ $ { } [ ] | ( ) \
/a\.b/.test("a.b"); // true — literal dot
/a\.b/.test("axb"); // false — a literal dot only matches "."
Characters That Need Escaping #
// Metacharacters that must be escaped to be treated as literals
const periksaTitikEscaped = /example\.com/; // Literal dot
const periksaBintang = /a\*b/; // Literal asterisk
const periksaKurung = /\(ok\)/; // Literal parentheses
const periksaSlash = /https:\/\//; // Forward slash in a literal
// Table of metacharacters that need escaping:
// . -> \. dot
// * -> \* asterisk
// + -> \+ plus
// ? -> \? question mark
// ^ -> \^ caret (outside character classes)
// $ -> \$ dollar
// { -> \{ opening curly brace
// } -> \} closing curly brace
// [ -> \[ opening square bracket
// ] -> \] closing square bracket
// | -> \| pipe
// ( -> \( opening parenthesis
// ) -> \) closing parenthesis
// \ -> \\ backslash
// Helper: escape all metacharacters in a dynamic string
function escapeRegex(teks: string): string {
return teks.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
}
const inputPengguna = "harga: 100.000 (diskon)";
const polaAman = new RegExp(escapeRegex(inputPengguna));
polaAman.test("total harga: 100.000 (diskon) ya"); // true
Metacharacters #
Metacharacters are characters with special meaning in a regex pattern — instead of matching themselves, they describe a position or character type:
. — Dot (Any Character)
#
// The dot matches ONE character of any kind except newline (\n)
/a.b/.test("acb"); // true — 'c' matches the dot
/a.b/.test("a b"); // true — a space matches the dot
/a.b/.test("a\nb"); // false — newlines don't match the dot (except with the s flag)
/a.b/.test("ab"); // false — there must be exactly one character between a and b
// With the s flag (dotAll) — the dot matches newlines too
/a.b/s.test("a\nb"); // true — the s flag enables dotAll mode
// ANTI-PATTERN: The dot is often used too widely
// /.*/ matches any string including unwanted ones
// Better to use a specific character class when possible
^ — Caret (Start of String/Line)
#
// ^ outside a character class = the start-of-string anchor
/^halo/.test("halo dunia"); // true — starts with "halo"
/^halo/.test("hai, halo"); // false — "halo" isn't at the start
// With the m flag (multiline) — ^ matches at the start of every line
const teksMultibaris = "baris satu\nbaris dua\nbaris tiga";
teksMultibaris.match(/^baris/gm);
// ["baris", "baris", "baris"] — matches at the start of every line
// ^ INSIDE a character class = negation (see the Character Classes section)
/[^abc]/.test("d"); // true — 'd' isn't a, b, or c
$ — Dollar (End of String/Line)
#
// $ = the end-of-string anchor
/dunia$/.test("halo dunia"); // true — ends with "dunia"
/dunia$/.test("dunia baru"); // false — "dunia" isn't at the end
// With the m flag — $ matches at the end of every line
const teks = "baris satu\nbaris dua";
teks.match(/\w+$/gm);
// ["satu", "dua"] — the last word of every line
// Full-format validation — use ^ and $ together
/^\d{5}$/.test("12345"); // true — exactly 5 digits, no more no less
/^\d{5}$/.test("123456"); // false — 6 digits
/^\d{5}$/.test("1234"); // false — 4 digits
\b and \B — Word Boundaries
#
// \b = word boundary — the position between \w and \W (or the string's start/end)
/\bkata\b/.test("ini kata"); // true — "kata" stands alone
/\bkata\b/.test("katakata"); // false — no word boundary
/\bkata\b/.test("kata-kata"); // true — a hyphen is \W
/\bkan\b/.test("akan"); // false — "kan" is part of "akan"
// \B = not a word boundary
/\Bkan\B/.test("seakan"); // true — "kan" is in the middle of a word
/\Bkan\B/.test("kan"); // false — "kan" stands alone
// Common use case: searching for whole words
const teks = "TypeScript tidak sama dengan JavaScript";
teks.match(/\bScript\b/g); // null — "Script" isn't a whole word
teks.match(/\bTypeScript\b/g); // ["TypeScript"] — a whole word
Character Classes #
Character classes [...] match one character from the defined set:
// [abc] — matches a, b, or c
/[aeiou]/.test("hello"); // true — 'e' and 'o' are vowels
/[aeiou]/.test("rhythm"); // false — no vowels
// [a-z] — a range: matches lowercase letters a through z
/[a-z]/.test("Hello"); // true — 'e', 'l', 'l', 'o' are in range
/[A-Z]/.test("hello"); // false — all lowercase
/[0-9]/.test("abc"); // false — no digits
/[0-9]/.test("abc5"); // true — '5' is in range
// Combining ranges
/[a-zA-Z0-9]/.test("Hello123"); // true — alphanumeric
// [^...] — negation: matches characters NOT in the class
/[^aeiou]/.test("rhythm"); // true — all characters aren't vowels
/[^0-9]/.test("abc"); // true — no digits
/[^0-9]/.test("123"); // false — all digits
Special Characters Inside [...]
#
// Inside [], some characters lose their special meaning
// . doesn't need escaping inside []
/[.]/.test("."); // true — a literal dot inside []
/[.]/.test("a"); // false — only matches a literal dot
// - must be in the first/last position or escaped to be literal
/[-+]/.test("-"); // true — a minus in the first position = literal
/[+\-]/.test("-"); // true — an escaped minus = literal
// ^ only has negation meaning in the first position
/[a^b]/.test("^"); // true — ^ in the middle = literal
/[^ab]/.test("c"); // true — ^ at the start = negation
Predefined Character Classes #
Character classes predefined as shorthand:
// \d — digit, equivalent to [0-9]
/\d/.test("5"); // true
/\d/.test("a"); // false
// \D — not a digit, equivalent to [^0-9]
/\D/.test("a"); // true
/\D/.test("5"); // false
// \w — word character, equivalent to [a-zA-Z0-9_]
/\w/.test("a"); // true
/\w/.test("_"); // true
/\w/.test("@"); // false
// NOTE: \w doesn't match non-ASCII characters like "é", "ñ", "ا"
// \W — not a word character
/\W/.test("@"); // true
/\W/.test("a"); // false
// \s — whitespace: space, tab (\t), newline (\n), carriage return (\r), form feed (\f)
/\s/.test(" "); // true — space
/\s/.test("\t"); // true — tab
/\s/.test("\n"); // true — newline
/\s/.test("a"); // false
// \S — not whitespace
/\S/.test("a"); // true
/\S/.test(" "); // false
Predefined Character Class Table #
| Identifier | Meaning | Equivalent To |
|---|---|---|
\d | Digit | [0-9] |
\D | Not a digit | [^0-9] |
\w | Word character | [a-zA-Z0-9_] |
\W | Not a word character | [^a-zA-Z0-9_] |
\s | Whitespace | [ \t\n\r\f\v] |
\S | Not whitespace | [^ \t\n\r\f\v] |
. | Any character (except \n) | [^\n] |
\n | Newline | — |
\t | Tab | — |
\r | Carriage return | — |
Unicode Property Escapes (the u Flag)
#
With the u flag, TypeScript/JavaScript supports matching by Unicode properties — very useful for multilingual text:
// \p{property} — matches characters with a certain Unicode property
// Requires the u flag
/\p{L}/u.test("A"); // true — a Letter
/\p{L}/u.test("1"); // false — not a letter
/\p{L}/u.test("ا"); // true — an Arabic letter
/\p{L}/u.test("你"); // true — a Chinese character
/\p{N}/u.test("5"); // true — a Number
/\p{P}/u.test("."); // true — Punctuation
/\p{Script=Arabic}/u.test("ا"); // true — an Arabic character
/\p{Script=Latin}/u.test("A"); // true — a Latin character
/\p{Script=Han}/u.test("你"); // true — a Han character (Chinese/Japanese/Korean)
/\p{Emoji}/u.test("😊"); // true — an emoji
/\p{Emoji}/u.test("A"); // false
// \P{} = the negation of \p{}
/\P{L}/u.test("1"); // true — not a letter
Quantifiers — Controlling Repetition #
Quantifiers determine how many times the preceding element must appear:
Basic Quantifiers #
// * — 0 or more (greedy)
/ab*c/.test("ac"); // true — 0 'b's
/ab*c/.test("abc"); // true — 1 'b'
/ab*c/.test("abbbc"); // true — 3 'b's
// + — 1 or more (greedy)
/ab+c/.test("ac"); // false — at least 1 'b' required
/ab+c/.test("abc"); // true — 1 'b'
/ab+c/.test("abbbc"); // true — 3 'b's
// ? — 0 or 1 (optional, greedy)
/colou?r/.test("color"); // true — without 'u'
/colou?r/.test("colour"); // true — with 'u'
// {n} — exactly n times
/\d{4}/.test("2025"); // true
/\d{4}/.test("202"); // false — fewer than 4
// {n,} — at least n times
/\d{3,}/.test("123"); // true
/\d{3,}/.test("12"); // false
// {n,m} — between n and m times (inclusive)
/\d{2,4}/.test("12"); // true — 2 digits
/\d{2,4}/.test("1234"); // true — 4 digits
/\d{2,4}/.test("12345"); // true — matches 4 digits at the start
/\d{2,4}/.test("1"); // false — 1 digit
Greedy vs Lazy #
const html = "<b>tebal</b> dan <i>miring</i>";
// Greedy — take as much as possible (default)
html.match(/<.+>/)?.[0]; // "<b>tebal</b> dan <i>miring</i>"
// Lazy — take as little as possible (add ? after the quantifier)
html.match(/<.+?>/)?.[0]; // "<b>"
// Table: Greedy → Lazy
// * → *? (0 or more, lazy)
// + → +? (1 or more, lazy)
// ? → ?? (0 or 1, lazy)
// {n,m} → {n,m}? (range, lazy)
// Real example: extract all HTML tags
const semuaTag = html.match(/<.+?>/g);
// ["<b>", "</b>", "<i>", "</i>"]
// vs greedy which only takes from the first < to the last >
const greedy = html.match(/<.+>/g);
// ["<b>tebal</b> dan <i>miring</i>"]
Grouping and Capturing #
(...) — Capturing Groups
#
// Capturing groups capture the matching part for later access
const tanggal = /(\d{4})-(\d{2})-(\d{2})/.exec("2025-05-07");
if (tanggal) {
console.log(tanggal[0]); // "2025-05-07" — full match
console.log(tanggal[1]); // "2025" — group 1
console.log(tanggal[2]); // "05" — group 2
console.log(tanggal[3]); // "07" — group 3
}
// Backreferences in a pattern — \1 refers to the contents of group 1
// Useful for finding repeated words
/(\b\w+\b) \1/.test("halo halo"); // true — the word is repeated
/(\b\w+\b) \1/.test("halo dunia"); // false — different words
// Backreferences in replace — $1, $2, etc.
"2025-05-07".replace(/(\d{4})-(\d{2})-(\d{2})/, "$3/$2/$1");
// "07/05/2025"
(?:...) — Non-capturing Groups
#
// Non-capturing groups — group without capturing an index
// Useful for grouping alternation or quantifiers without overhead
// Without non-capturing: "ab" is captured as a group
/(ab)+/.exec("ababab")?.[1]; // "ab" — only the LAST capture
// With non-capturing: no captures, more efficient
/(?:ab)+/.exec("ababab")?.[1]; // undefined — no group
// Common case: grouping alternation
/(?:https?|ftp):\/\//.test("https://example.com"); // true
/(?:https?|ftp):\/\//.test("ftp://files.com"); // true
(?<name>...) — Named Capturing Groups
#
// Named groups — access via .groups.name instead of indices
const formatISO = /(?<tahun>\d{4})-(?<bulan>\d{2})-(?<hari>\d{2})/;
const hasilISO = formatISO.exec("tanggal: 2025-05-07");
if (hasilISO?.groups) {
const { tahun, bulan, hari } = hasilISO.groups;
// tahun: string, bulan: string, hari: string
console.log(`${hari} ${['','Jan','Feb','Mar','Apr','Mei','Jun','Jul','Agu','Sep','Okt','Nov','Des'][parseInt(bulan)]} ${tahun}`);
// "07 Mei 2025"
}
// Named backreferences — \k<name>
/(?<kata>\b\w+\b) \k<kata>/.test("halo halo"); // true — the word is repeated
Alternation #
// | — OR: matches one of the separated patterns
/kucing|anjing/.test("saya punya kucing"); // true
/kucing|anjing/.test("saya punya anjing"); // true
/kucing|anjing/.test("saya punya ikan"); // false
// Alternation inside a group — limits the scope of |
/^(senin|selasa|rabu|kamis|jumat)$/.test("senin"); // true
/^(senin|selasa|rabu|kamis|jumat)$/.test("sabtu"); // false
/^(senin|selasa|rabu|kamis|jumat)$/.test("senin "); // false — there's a space
// Alternation tries from left to right — order matters!
// "ab" vs "a" — "ab" must be on the left so it can match first
/ab|a/.exec("ab")?.[0]; // "ab" — "ab" is tried first
/a|ab/.exec("ab")?.[0]; // "a" — "a" matches first!
Assertions #
Assertions match positions in a string, not characters:
// ^ and $ were already covered in the Metacharacters section above
// \b — word boundary
/\bTypeScript\b/.test("TypeScript itu keren"); // true
/\bScript\b/.test("TypeScript"); // false — part of a word
// \B — non-word boundary (the opposite of \b)
/\Btype\B/.test("prototype"); // true — "type" is in the middle of a word
/\Btype\B/.test("type"); // false — "type" stands alone
// Zero-width: assertions don't consume characters
// This means they match at a POSITION, not a character
const hasil = "abc".match(/(?=b)/);
// Matches at the position before 'b', but doesn't consume 'b'
Lookahead and Lookbehind (Complete) #
Lookaround is a zero-width assertion that checks context without including it in the result:
// =============================================
// LOOKAHEAD
// =============================================
// (?=pola) — Positive Lookahead: matches if followed by a pattern
"100px 200em 300px".match(/\d+(?=px)/g);
// ["100", "300"] — numbers followed by "px"
// (?!pola) — Negative Lookahead: matches if NOT followed by a pattern
"100px 200em 300px".match(/\d+(?!px)(?!\d)/g);
// ["200"] — numbers not followed by "px"
// =============================================
// LOOKBEHIND
// =============================================
// (?<=pola) — Positive Lookbehind: matches if preceded by a pattern
"$100 €200 £300".match(/(?<=\$)\d+/g);
// ["100"] — numbers preceded by "$"
// (?<!pola) — Negative Lookbehind: matches if NOT preceded by a pattern
"$100 €200 £300".match(/(?<!\$)\d+/g);
// ["200", "300"] — numbers not preceded by "$"
// (ignoring the fact that this can be more complex in real implementations)
// =============================================
// COMBINING LOOKAROUND
// =============================================
// Extract values between specific tags
const xml = "<nama>Budi Santoso</nama>";
xml.match(/(?<=<nama>).+?(?=<\/nama>)/)?.[0];
// "Budi Santoso" — without the tags
// Password validation with lookahead:
// min 8 characters, at least 1 uppercase, 1 lowercase, 1 digit
const regexPassword = /^(?=.*[A-Z])(?=.*[a-z])(?=.*\d).{8,}$/;
regexPassword.test("Password1"); // true
regexPassword.test("password1"); // false — no uppercase
regexPassword.test("PASSWORD1"); // false — no lowercase
regexPassword.test("Pass1"); // false — fewer than 8 characters
Quick Reference Table of All Identifiers #
LITERAL CHARACTERS
abc — Literal letters/numbers
\. — Literal dot (escaped metacharacter)
METACHARACTERS
. — Any character except \n (or everything with the s flag)
^ — Start of string (or start of line with the m flag)
$ — End of string (or end of line with the m flag)
\b — Word boundary
\B — Non-word boundary
CHARACTER CLASSES
[abc] — One of: a, b, or c
[^abc] — Not a, b, or c
[a-z] — Lowercase letter range
[A-Z] — Uppercase letter range
[0-9] — Digit range
[a-zA-Z0-9] — Combined ranges
PREDEFINED CLASSES
\d — Digit [0-9]
\D — Not a digit [^0-9]
\w — Word char [a-zA-Z0-9_]
\W — Not a word char
\s — Whitespace
\S — Not whitespace
\p{L} — Unicode Letter (u flag)
\p{N} — Unicode Number (u flag)
\p{Emoji} — Emoji (u flag)
QUANTIFIERS (GREEDY)
* — 0 or more
+ — 1 or more
? — 0 or 1
{n} — Exactly n times
{n,} — At least n times
{n,m} — Between n and m times
QUANTIFIERS (LAZY — add ?)
*? — 0 or more (lazy)
+? — 1 or more (lazy)
?? — 0 or 1 (lazy)
{n,m}? — Between n and m (lazy)
GROUPING
(pola) — Capturing group
(?:pola) — Non-capturing group
(?<nama>) — Named capturing group
(a|b) — Alternation inside a group
BACKREFERENCES
\1 \2 ... — Backreference by index
\k<nama> — Backreference by name
$1 $2 ... — In replace (by index)
$<nama> — In replace (by name)
LOOKAROUND
(?=pola) — Positive lookahead
(?!pola) — Negative lookahead
(?<=pola) — Positive lookbehind
(?<!pola) — Negative lookbehind
ESCAPE SEQUENCES
\n — Newline
\t — Tab
\r — Carriage return
\0 — Null character
\uXXXX — Unicode character (hex)
\u{XXXXX} — Unicode codepoint (u flag)
Summary #
- Escape metacharacters with
\when you want to match them as literals — the characters. * + ? ^ $ { } [ ] | ( ) \all have special meanings.- Use an
escapeRegex()function when building patterns from user input or dynamic data to prevent metacharacters from accidentally changing the pattern’s behavior.[...]matches one character — not a character sequence;[abc]matches ‘a’ or ‘b’ or ‘c’, not the string “abc”.^inside[^...]is negation, not an anchor —[^abc]means “any character except a, b, c”.\wdoesn’t support non-ASCII characters — Arabic letters, Javanese script, or accented characters don’t match\w; use\p{L}with theuflag for correct multilingual matching.- Greedy is the default — quantifiers
+,*,?,{n,m}take as much as possible; add?after them (+?,*?) for lazy matching that takes as little as possible.- Named capturing groups
(?<nama>pola)are far easier to read than numeric indices — use them for patterns with more than two groups.- Lookaround is zero-width — it checks the context around a position without consuming characters, so it doesn’t appear in the match result.
- Alternation order matters —
|tries from left to right and stops at the first match; put longer/more specific patterns on the left so they aren’t overshadowed by shorter patterns.