X Xerobit

JavaScript Regex Flags — g, i, m, s, u, and v Explained

JavaScript regex flags change how patterns match. Learn when to use global /g, case-insensitive /i, multiline /m, dotAll /s, unicode /u and the new /v flag, plus how the...

Mian Ali Khalid · · 4 min read
Use the tool
Regex Tester
Test regular expressions with live match highlighting and explanation.
Open Regex Tester →

JavaScript regex flags modify matching behavior. The most common bug: reusing a /g regex between calls without resetting lastIndex. This causes intermittent “no match” failures that are hard to debug.

Test your patterns with the Regex Tester.

All JavaScript regex flags

const re = /pattern/flags;  // Literal syntax
const re = new RegExp('pattern', 'flags');  // Constructor

// Available flags:
// g  — global: find all matches, not just first
// i  — case-insensitive
// m  — multiline: ^ and $ match line boundaries, not string boundaries
// s  — dotAll: . matches newline (\n)
// u  — unicode: enables Unicode escape sequences \u{...}
// v  — unicodeSets (ES2024): upgraded unicode with set notation
// d  — hasIndices: .exec() returns start/end indices for groups
// y  — sticky: match from lastIndex position only

/g — global flag and lastIndex

The global flag makes .exec() stateful — it remembers where it left off using lastIndex:

const re = /\d+/g;
const str = 'abc 123 def 456';

// .exec() with /g advances lastIndex:
re.exec(str);  // ['123'], re.lastIndex = 7
re.exec(str);  // ['456'], re.lastIndex = 15
re.exec(str);  // null,    re.lastIndex = 0 (reset)

// Common bug: reusing /g regex across calls
function findFirstNumber(str) {
  const re = /\d+/g;
  return re.exec(str)?.[0];
}
findFirstNumber('abc 123');  // "123" ✓
findFirstNumber('abc 123');  // "123" ✓ (new regex each call, fine)

// BUGGY version: regex defined outside function
const DIGIT_RE = /\d+/g;
function findFirstNumberBuggy(str) {
  return DIGIT_RE.exec(str)?.[0];
}
findFirstNumberBuggy('abc 123');  // "123" ✓ (lastIndex: 0 → 7)
findFirstNumberBuggy('abc 456');  // undefined ✗ (starts at lastIndex=7, "456" is at index 4, miss!)
findFirstNumberBuggy('abc 789');  // "789" ✓ (lastIndex reset after null)

Fix: Use .test() or .exec() without /g when only checking a single match. Or reset lastIndex explicitly:

const DIGIT_RE = /\d+/g;

function findFirstNumber(str) {
  DIGIT_RE.lastIndex = 0;  // Reset before use
  return DIGIT_RE.exec(str)?.[0];
}

/g with String.matchAll()

matchAll() is the safe way to iterate all matches with /g:

const str = 'cat bat sat';
const matches = [...str.matchAll(/[cbsr]at/g)];
// [['cat', index:0], ['bat', index:4], ['sat', index:8]]

// Capture groups:
const dates = 'Born 2001-05-12, died 2090-12-31';
for (const match of dates.matchAll(/(\d{4})-(\d{2})-(\d{2})/g)) {
  const [full, year, month, day] = match;
  console.log(`${year}/${month}/${day}`);
}
// 2001/05/12
// 2090/12/31

/i — case-insensitive

/hello/i.test('HELLO');    // true
/hello/i.test('Hello');    // true
/^[a-z]+$/i.test('ABCdef');  // true (same as [a-zA-Z])

// Common use: case-insensitive search
'The Quick Brown Fox'.match(/quick/i);  // ['Quick']

/m — multiline

Without /m, ^ and $ match start/end of the entire string. With /m, they match start/end of each line:

const text = 'line 1\nline 2\nline 3';

/^line/g.exec(text);   // ['line'] at position 0 only

const lines = text.match(/^line.+$/mg);
// ['line 1', 'line 2', 'line 3']

// Replace only at start of lines:
text.replace(/^/mg, '> ');
// "> line 1\n> line 2\n> line 3"

/s — dotAll (single-line mode)

By default, . does NOT match \n. The /s flag makes . match everything:

// Without /s:
/<div>.*<\/div>/.test('<div>hello\nworld</div>');  // false (. stops at \n)

// With /s:
/<div>.*<\/div>/s.test('<div>hello\nworld</div>');  // true

// Practical: match multiline HTML tags:
const html = '<p>\n  Hello world\n</p>';
const match = html.match(/<p>(.*?)<\/p>/s);
// match[1] = "\n  Hello world\n"

/u — unicode

// Without /u: emoji counted as 2 chars (surrogate pairs)
'😀'.length;    // 2
/^.$/.test('😀');  // false (. matches one code unit, not code point)

// With /u: emoji is one character
/^.$/u.test('😀');  // true

// Unicode category escapes (require /u):
/\p{Letter}/u.test('ñ');   // true
/\p{Number}/u.test('②');   // true (Unicode numbers)
/\p{Emoji}/u.test('😀');    // true

// Unicode property escapes:
'abc αβγ'.match(/\p{Script=Greek}+/gu);  // ['αβγ']

/v — unicodeSets (ES2024)

The /v flag is an upgraded version of /u with set operations:

// Intersection of unicode properties:
/[\p{Letter}&&\p{ASCII}]/v.test('a');  // true (ASCII letter)
/[\p{Letter}&&\p{ASCII}]/v.test('ñ');  // false (not ASCII)

// Subtraction:
/[\p{Letter}--\p{ASCII}]/v.test('ñ');  // true (non-ASCII letter)

// String literal inside character class:
/[\q{hello|world}]/v.test('hello');    // true

// More accurate multichar sequences:
/[🏴󠁧󠁢󠁳󠁣󠁴󠁿]/v.test('🏴󠁧󠁢󠁳󠁣󠁴󠁿');  // true (Scottish flag)

/d — indices (ES2022)

// Returns start and end index for each group:
const match = 'hello world'.match(/(?<word>\w+)/d);
// match.indices[0] = [0, 5]      (full match)
// match.indices.groups.word = [0, 5]  (named group)

// Useful for code editors: highlight where each capture is

Related posts

Related tool

Regex Tester

Test regular expressions with live match highlighting and explanation.

Written by Mian Ali Khalid. Part of the Dev Productivity pillar.