Skip to main content

How to Create Regular Expression Patterns and Flags in JavaScript

Regular expressions are one of the most powerful tools in a developer's toolkit. They provide a concise, flexible way to search, match, and manipulate text using patterns. Whether you are validating an email address, extracting data from a log file, replacing text in a document, or parsing complex string formats, regular expressions can accomplish in a single line what would otherwise require dozens of lines of procedural code.

JavaScript has built-in support for regular expressions through the RegExp object and dedicated syntax. This guide covers the two ways to create regular expressions, explains every available flag and when to use each one, and introduces the fundamental methods for searching strings with patterns.

Creating RegExp: Literal /pattern/flags and Constructor new RegExp()

JavaScript provides two ways to create a regular expression. Both produce a RegExp object, but they differ in syntax, timing, and use cases.

Literal Syntax: /pattern/flags

The most common way to create a regular expression is with the literal syntax, using forward slashes as delimiters:

const regex = /hello/;
const regexWithFlags = /hello/gi;

The pattern goes between the slashes, and flags (optional) go immediately after the closing slash. No quotes are used.

const digitPattern = /\d+/g;         // One or more digits, global search
const emailPattern = /\S+@\S+\.\S+/; // Simple email pattern
const wordBoundary = /\bcat\b/i; // The word "cat", case-insensitive

The literal syntax is evaluated at parse time (when the JavaScript engine reads the source code). This means the pattern is fixed and cannot be changed at runtime.

Constructor Syntax: new RegExp(pattern, flags)

The constructor syntax creates a regular expression from strings:

const regex = new RegExp('hello');
const regexWithFlags = new RegExp('hello', 'gi');

The first argument is the pattern as a string, and the second (optional) argument is the flags as a string.

const digitPattern = new RegExp('\\d+', 'g');
const wordBoundary = new RegExp('\\bcat\\b', 'i');
warning

Notice the double backslashes in the constructor syntax. Because the pattern is a regular JavaScript string, backslashes need to be escaped. The string '\\d' produces the two-character sequence \d, which the RegExp engine then interprets as "any digit." A single backslash '\d' would be interpreted by the JavaScript string parser first, potentially producing an unintended character.

// These are equivalent:
const regex1 = /\d+\.\d+/; // Literal: backslashes are for regex
const regex2 = new RegExp('\\d+\\.\\d+'); // Constructor: double backslashes

// ❌ Common mistake: single backslash in constructor
const broken = new RegExp('\d+');
// '\d' in a string is just 'd' (no special meaning)
// This creates the regex /d+/, not /\d+/

When to Use Each Syntax

Use literal syntax when the pattern is known at development time and will not change:

// Fixed patterns: use literals
const isEmail = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
const hasDigit = /\d/;
const isHexColor = /^#[0-9a-f]{6}$/i;

Use the constructor when the pattern needs to be dynamic, built from variables, or determined at runtime:

// Dynamic pattern: must use constructor
function findWord(text, word) {
const regex = new RegExp(`\\b${word}\\b`, 'gi');
return text.match(regex);
}

let ris = findWord('The cat sat on the mat', 'cat');
console.log(ris) // ["cat"]

// Pattern from user input
function highlightSearch(text, searchTerm) {
const escaped = searchTerm.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
const regex = new RegExp(escaped, 'gi');
return text.replace(regex, '<mark>$&</mark>');
}

Escaping User Input for Dynamic Patterns

When building regex patterns from user input, special regex characters must be escaped to prevent them from being interpreted as pattern syntax:

function escapeRegExp(string) {
return string.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
}

// Without escaping:
const userInput = 'price: $9.99 (USD)';
const broken = new RegExp(userInput); // Syntax error or wrong behavior
// $, ., (, ) are all special regex characters

// With escaping:
const safe = new RegExp(escapeRegExp(userInput));
// Creates: /price: \$9\.99 \(USD\)/

The $& in the replacement string refers to the entire matched substring. This escapeRegExp function places a backslash before every special character, making it safe to use in a regular expression.

RegExp Objects Are Stateful

An important detail: RegExp objects with the g (global) or y (sticky) flag maintain internal state through the lastIndex property. This can cause unexpected behavior when reusing a regex:

const regex = /\d+/g;

console.log(regex.test('abc 123 def')); // true (finds "123")
console.log(regex.lastIndex); // 7 (position after the match)

console.log(regex.test('abc 123 def')); // true (finds... wait, continues from index 7)
console.log(regex.lastIndex); // 7... actually let me trace through

// Let me show the issue more clearly:
const regex2 = /cat/g;

console.log(regex2.test('cat')); // true
console.log(regex2.lastIndex); // 3
console.log(regex2.test('cat')); // false! Starts searching from index 3
console.log(regex2.lastIndex); // 0 (reset after failing)
console.log(regex2.test('cat')); // true again

This statefulness matters when using test() or exec() in loops or repeated calls. We will cover this in detail later.

Comparing the Two Syntaxes

FeatureLiteral /pattern/flagsConstructor new RegExp()
Evaluated atParse timeRuntime
Pattern sourceFixed in codeCan be dynamic (variables, expressions)
Backslash handlingSingle \Double \\ (string escaping)
ReadabilityHigher for simple patternsLower due to string escaping
Use caseKnown, static patternsDynamic, user-generated patterns
Syntax errorsCaught at parse timeCaught at runtime

Flags: g, i, m, s, u, v, y, d

Flags modify how the regular expression engine processes the pattern. They are placed after the closing slash in literal syntax or passed as the second argument to the constructor. Multiple flags can be combined in any order.

const regex = /pattern/gim;
// or
const regex = new RegExp('pattern', 'gim');

Without the g flag, a regex search stops after finding the first match. With g, it finds all matches in the string.

const text = 'cat bat sat mat';

// Without g: only the first match
console.log(text.match(/[a-z]at/));
// ["cat", index: 0, ...]

// With g: all matches
console.log(text.match(/[a-z]at/g));
// ["cat", "bat", "sat", "mat"]

The g flag is essential when you want to find every occurrence, count matches, or replace all instances:

const text = 'Hello World Hello JavaScript';

// Replace only the first occurrence (default)
console.log(text.replace(/Hello/, 'Hi'));
// "Hi World Hello JavaScript"

// Replace all occurrences
console.log(text.replace(/Hello/g, 'Hi'));
// "Hi World Hi JavaScript"

i: Case-Insensitive

By default, regex matching is case-sensitive. The i flag makes the search ignore case:

console.log(/hello/.test('Hello World'));  // false
console.log(/hello/i.test('Hello World')); // true

const text = 'JavaScript javascript JAVASCRIPT';

console.log(text.match(/javascript/gi));
// ["JavaScript", "javascript", "JAVASCRIPT"]

m: Multiline Mode

The m flag changes the behavior of ^ and $ anchors. Without m, they match the start and end of the entire string. With m, they match the start and end of each line.

const text = `First line
Second line
Third line`;

// Without m: ^ matches only the start of the entire string
console.log(text.match(/^\w+/g));
// ["First"]

// With m: ^ matches the start of each line
console.log(text.match(/^\w+/gm));
// ["First", "Second", "Third"]
const csv = `name,age
Alice,30
Bob,25`;

// Match lines that start with a capital letter followed by a comma
console.log(csv.match(/^[A-Z][a-z]+,\d+$/gm));
// ["Alice,30", "Bob,25"]

The m flag only affects ^ and $. It does not change how . works (use the s flag for that).

s: Dotall Mode

By default, the dot . matches any character except newline characters (\n, \r, etc.). The s flag makes . match every character, including newlines.

const html = `<div>
Hello World
</div>`;

// Without s: . doesn't match newlines, so this fails
console.log(html.match(/<div>.*<\/div>/));
// null

// With s: . matches newlines too
console.log(html.match(/<div>.*<\/div>/s));
// ["<div>\n Hello World\n</div>"]

Without the s flag, you would need a workaround like [\s\S] (any whitespace or non-whitespace character, i.e., everything):

// Old workaround (before the s flag existed)
console.log(html.match(/<div>[\s\S]*<\/div>/));
// Works, but the s flag is cleaner

u: Unicode Mode

The u flag enables full Unicode support. Without it, JavaScript regex treats characters as 16-bit code units, which means characters outside the Basic Multilingual Plane (like emoji and many CJK characters) are treated as two separate units.

// Without u: emoji is treated as two code units
console.log('😀'.match(/./));
// Matches only half the emoji (a surrogate character)

// With u: emoji is treated as one character
console.log('😀'.match(/./u));
// ["😀"] (correctly matches the full emoji)

The u flag also enables Unicode property escapes (\p{...}):

// Match any Unicode letter (not just ASCII a-z, A-Z)
console.log('café résumé naïve'.match(/\p{Letter}+/gu));
// ["café", "résumé", "naïve"]

// Match any Unicode digit
console.log('Price: ١٢٣'.match(/\p{Number}+/gu));
// ["١٢٣"] (Arabic-Indic numerals)

// Match any emoji
console.log('Hello 👋 World 🌍'.match(/\p{Emoji}/gu));
// ["👋", "🌍"]

The u flag also makes the regex engine strict about invalid escape sequences that would otherwise be silently accepted:

// Without u: invalid escape \a is treated as literal "a" (sloppy)
console.log(/\a/.test('a')); // true

// With u: invalid escape \a throws a SyntaxError (strict)
// /\a/u (SyntaxError: Invalid escape)
tip

Always use the u flag when working with text that may contain non-ASCII characters (international text, emoji, mathematical symbols). It ensures correct character handling and enables powerful Unicode property escapes.

v: Unicode Sets Mode (ES2024)

The v flag is a newer, more powerful version of u. It enables Unicode set notation inside character classes and adds support for properties of strings (multi-character properties):

// Set operations inside character classes
// Intersection: match characters that are both Greek AND letters
/[\p{Script=Greek}&&\p{Letter}]/v

// Subtraction: match digits except ASCII digits
/[\p{Number}--[0-9]]/v

// String properties: match multi-character sequences
/\p{Emoji_Keycap_Sequence}/v

The v flag is a superset of u. You cannot use both simultaneously, and v is preferred for new code when targeting modern environments:

// ❌ Cannot combine u and v
// /pattern/uv (SyntaxError)

// ✅ Use v instead of u for modern code
const regex = /\p{Letter}+/gv;

y: Sticky Mode

The y flag makes the regex match only at the exact position indicated by the lastIndex property. Unlike g, which searches forward from lastIndex to find a match anywhere, y requires the match to start exactly at lastIndex.

const regex = /\d+/y;
const text = '123 456 789';

regex.lastIndex = 0;
console.log(regex.exec(text)); // ["123"] (match at index 0)

regex.lastIndex = 3;
console.log(regex.exec(text)); // null (index 3 is a space, not a digit)

regex.lastIndex = 4;
console.log(regex.exec(text)); // ["456"] (match at index 4)

Compare with g:

const regexG = /\d+/g;
const text = '123 456 789';

regexG.lastIndex = 3;
console.log(regexG.exec(text)); // ["456"] (g searches FORWARD from index 3)
// It skips the space and finds "456"

const regexY = /\d+/y;
regexY.lastIndex = 3;
console.log(regexY.exec(text)); // null (y requires match EXACTLY at index 3)

The y flag is useful for tokenizers and parsers where you need to match patterns at specific positions:

function tokenize(input) {
const tokens = [];
const patterns = [
{ type: 'NUMBER', regex: /\d+/y },
{ type: 'PLUS', regex: /\+/y },
{ type: 'MINUS', regex: /-/y },
{ type: 'SPACE', regex: /\s+/y }
];

let pos = 0;

while (pos < input.length) {
let matched = false;

for (const pattern of patterns) {
pattern.regex.lastIndex = pos;
const match = pattern.regex.exec(input);

if (match) {
if (pattern.type !== 'SPACE') {
tokens.push({ type: pattern.type, value: match[0] });
}
pos = pattern.regex.lastIndex;
matched = true;
break;
}
}

if (!matched) {
throw new Error(`Unexpected character at position ${pos}: "${input[pos]}"`);
}
}

return tokens;
}

console.log(tokenize('12 + 34 - 5'));
// [
// { type: 'NUMBER', value: '12' },
// { type: 'PLUS', value: '+' },
// { type: 'NUMBER', value: '34' },
// { type: 'MINUS', value: '-' },
// { type: 'NUMBER', value: '5' }
// ]

d: Has Indices (ES2022)

The d flag causes the regex engine to generate index information for each capturing group. Without d, you only get the start index of the overall match. With d, you get a .indices property that contains the start and end positions of each group.

const regex = /(\d{4})-(\d{2})-(\d{2})/d;
const match = regex.exec('Date: 2024-03-15');

console.log(match[0]); // "2024-03-15"
console.log(match[1]); // "2024"
console.log(match[2]); // "03"
console.log(match[3]); // "15"

// Without d, match.indices is undefined
// With d:
console.log(match.indices[0]); // [6, 16] (full match start/end)
console.log(match.indices[1]); // [6, 10] (group 1 (year))
console.log(match.indices[2]); // [11, 13] (group 2 (month))
console.log(match.indices[3]); // [14, 16] (group 3 (day))

With named groups, the indices also have a groups property:

const regex = /(?<year>\d{4})-(?<month>\d{2})-(?<day>\d{2})/d;
const match = regex.exec('Date: 2024-03-15');

console.log(match.indices.groups.year); // [6, 10]
console.log(match.indices.groups.month); // [11, 13]
console.log(match.indices.groups.day); // [14, 16]

This is particularly useful for syntax highlighters, code editors, and tools that need to know the exact positions of matched groups to apply formatting or transformations.

Flags Summary

FlagNamePurpose
gGlobalFind all matches, not just the first
iCase-insensitiveIgnore uppercase/lowercase differences
mMultiline^ and $ match line boundaries, not just string boundaries
sDotall. matches newline characters too
uUnicodeFull Unicode support, proper surrogate pair handling
vUnicode SetsExtended Unicode support with set operations (ES2024)
yStickyMatch only at the exact lastIndex position
dHas IndicesProvide start/end indices for capturing groups (ES2022)

Checking a Regex's Flags

You can inspect which flags a regex has through its properties:

const regex = /hello/gim;

console.log(regex.flags); // "gim"
console.log(regex.global); // true
console.log(regex.ignoreCase); // true
console.log(regex.multiline); // true
console.log(regex.dotAll); // false
console.log(regex.unicode); // false
console.log(regex.sticky); // false
console.log(regex.hasIndices); // false

// The source (pattern without flags)
console.log(regex.source); // "hello"

First Search: str.match() and regexp.test()

With a regex created and flags configured, you need methods to actually search strings. The two most commonly used methods for initial searching are str.match() (which returns match data) and regexp.test() (which returns a boolean).

regexp.test(str): Does It Match?

The test() method is the simplest way to check if a pattern exists in a string. It returns true or false:

const hasDigit = /\d/;

console.log(hasDigit.test('hello')); // false
console.log(hasDigit.test('hello123')); // true
console.log(hasDigit.test('')); // false

test() is ideal for validation:

function isValidUsername(username) {
// 3-20 characters, letters, digits, and underscores only
return /^[a-zA-Z0-9_]{3,20}$/.test(username);
}

console.log(isValidUsername('alice_99')); // true
console.log(isValidUsername('ab')); // false (too short)
console.log(isValidUsername('hello world'));// false (contains space)

function isStrongPassword(password) {
const hasUpperCase = /[A-Z]/.test(password);
const hasLowerCase = /[a-z]/.test(password);
const hasDigit = /\d/.test(password);
const hasSpecial = /[!@#$%^&*(),.?":{}|<>]/.test(password);
const isLongEnough = password.length >= 8;

return hasUpperCase && hasLowerCase && hasDigit && hasSpecial && isLongEnough;
}

console.log(isStrongPassword('Abc123!@')); // true
console.log(isStrongPassword('password')); // false

The lastIndex Trap with test()

When using test() with the g or y flag, the regex remembers where the last match ended and starts the next search from there. This causes alternating true/false results when testing the same string repeatedly:

// ❌ Unexpected behavior with the g flag
const regex = /cat/g;

console.log(regex.test('cat')); // true (lastIndex → 3)
console.log(regex.test('cat')); // false (starts at 3, nothing found, lastIndex → 0)
console.log(regex.test('cat')); // true (starts at 0 again)
console.log(regex.test('cat')); // false

This is the most common regex pitfall in JavaScript. Solutions:

// ✅ Solution 1: Don't use the g flag with test() for simple checks
const regex = /cat/; // No g flag
console.log(regex.test('cat')); // true
console.log(regex.test('cat')); // true (always)

// ✅ Solution 2: Create a new regex each time
function hasCat(str) {
return /cat/g.test(str); // New regex object each call
}

// ✅ Solution 3: Reset lastIndex manually
const regex2 = /cat/g;
regex2.lastIndex = 0;
console.log(regex2.test('cat')); // true
warning

Do not use the g flag with test() unless you intentionally need stateful, sequential matching. For simple "does it match?" checks, omit the g flag. This is one of the most frequently encountered regex bugs in JavaScript.

str.match(regexp): Finding Matches

The match() method searches a string for matches against a regex and returns the results. Its behavior changes dramatically based on whether the g flag is present.

Without g flag: Returns detailed information about the first match:

const result = 'Hello World 123'.match(/\d+/);

console.log(result[0]); // "123" (the matched text)
console.log(result.index); // 12 (position of the match)
console.log(result.input); // "Hello World 123" (the original string)

// If no match is found, returns null
console.log('Hello World'.match(/\d+/)); // null

With capturing groups, the array includes the captured values:

const dateStr = 'Today is 2024-03-15';
const result = dateStr.match(/(\d{4})-(\d{2})-(\d{2})/);

console.log(result[0]); // "2024-03-15" (full match)
console.log(result[1]); // "2024" (first group)
console.log(result[2]); // "03" (second group)
console.log(result[3]); // "15" (third group)
console.log(result.index); // 9

With g flag: Returns an array of all matched strings, but without group details or index information:

const text = 'cat bat sat mat';
const result = text.match(/[a-z]at/g);

console.log(result); // ["cat", "bat", "sat", "mat"]
// No .index, no .input, no capturing group details

If no match is found, match() returns null regardless of flags:

console.log('hello'.match(/\d+/g)); // null
console.log('hello'.match(/\d+/)); // null

Always Check for null

Since match() returns null when there is no match, always check before using the result:

// ❌ TypeError if no match
const count = text.match(/\d+/g).length;

// ✅ Safe: handle null
const matches = text.match(/\d+/g);
const count = matches ? matches.length : 0;

// ✅ Alternative: nullish coalescing with empty array
const count2 = (text.match(/\d+/g) || []).length;

// ✅ Modern: nullish coalescing operator
const count3 = (text.match(/\d+/g) ?? []).length;

Practical Examples

Extracting all URLs from text:

const text = 'Visit https://example.com or http://test.org for more info.';
const urls = text.match(/https?:\/\/[^\s]+/g);

console.log(urls);
// ["https://example.com", "http://test.org"]

Counting word occurrences:

function countOccurrences(text, word) {
const escaped = word.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
const regex = new RegExp(`\\b${escaped}\\b`, 'gi');
const matches = text.match(regex);
return matches ? matches.length : 0;
}

const article = 'The cat sat on the mat. The cat was happy.';
console.log(countOccurrences(article, 'the')); // 3
console.log(countOccurrences(article, 'cat')); // 2
console.log(countOccurrences(article, 'dog')); // 0

Validating input formats:

// Phone number validation (US format)
function isValidPhone(phone) {
return /^\(?(\d{3})\)?[-.\s]?(\d{3})[-.\s]?(\d{4})$/.test(phone);
}

console.log(isValidPhone('(555) 123-4567')); // true
console.log(isValidPhone('555.123.4567')); // true
console.log(isValidPhone('5551234567')); // true
console.log(isValidPhone('123')); // false

// IPv4 address validation
function isValidIPv4(ip) {
const octet = '(25[0-5]|2[0-4]\\d|[01]?\\d\\d?)';
const regex = new RegExp(`^${octet}\\.${octet}\\.${octet}\\.${octet}$`);
return regex.test(ip);
}

console.log(isValidIPv4('192.168.1.1')); // true
console.log(isValidIPv4('256.1.1.1')); // false
console.log(isValidIPv4('1.2.3')); // false

match() vs. test(): Choosing the Right Method

Featureregexp.test(str)str.match(regexp)
ReturnsbooleanArray or null
Use case"Does it match?""What did it match?"
PerformanceFaster (stops at first match)Slower (builds result array)
Capturing groupsNot returnedReturned (without g)
Match positionsNot returnedReturned (without g)
All matchesN/AWith g flag
// Just checking existence? Use test()
if (/\d/.test(input)) {
console.log('Contains a digit');
}

// Need the matched value? Use match()
const digits = input.match(/\d+/);
if (digits) {
console.log('Found number:', digits[0]);
}

// Need all matches? Use match() with g
const allNumbers = input.match(/\d+/g);

A Preview of Other Methods

While test() and match() are the starting points, JavaScript provides additional regex methods covered in later guides:

// str.matchAll(regexp): Iterator of all matches with full details
for (const match of text.matchAll(/(\d+)/g)) {
console.log(`Found "${match[0]}" at index ${match.index}`);
}

// str.search(regexp): Returns the index of the first match
console.log('hello 42'.search(/\d+/)); // 6

// str.replace(regexp, replacement): Replace matches
console.log('2024-03-15'.replace(/(\d{4})-(\d{2})-(\d{2})/, '$2/$3/$1'));
// "03/15/2024"

// regexp.exec(str): Detailed match with stateful iteration
let match;
const regex = /\d+/g;
while ((match = regex.exec('a1 b2 c3')) !== null) {
console.log(`${match[0]} at ${match.index}`);
}
// "1 at 1", "2 at 4", "3 at 7"

Summary

Regular expressions in JavaScript are created through two syntaxes, modified by flags, and applied with search methods:

  • Literal syntax (/pattern/flags) is used for static, known patterns. It is evaluated at parse time and requires no string escaping for backslashes.
  • Constructor syntax (new RegExp(pattern, flags)) is used for dynamic patterns built from variables or user input. Backslashes must be doubled because the pattern is a string. Always escape user input with a dedicated function before using it in a regex.
  • Flags modify regex behavior: g for global (all matches), i for case-insensitive, m for multiline anchors, s for dotall (. matches newlines), u for Unicode support, v for Unicode sets (ES2024), y for sticky positional matching, and d for match indices (ES2022).
  • regexp.test(str) returns a boolean and is ideal for validation. Avoid using the g flag with test() unless you need stateful sequential matching.
  • str.match(regexp) returns match details. Without g, it returns the first match with index and group information. With g, it returns an array of all matched strings. Always check for null before using the result.