Skip to content
DevPenh

JavaScript Regex Flags Explained: g, i, m, s, u, v, y, d

What each JavaScript regex flag changes, with tested examples: g with lastIndex and matchAll, m vs s, u and v for Unicode, sticky y for tokenizers, and d for indices.

By Yorn Chanpenh ยท Last reviewed

Flags are the letters after the closing slash of a JavaScript regular expression, as in /cat/gi. They do not change the pattern; they change how it is applied: how many matches are found, what ^, $ and . mean, and how Unicode text is read. This guide covers all eight flags with examples whose output was checked on Node.js 24.21.0. Browsers use the same rules.

FlagNamePropertyWhat it changes
gglobalglobalFind every match, not just the first
iignore caseignoreCasea matches A
mmultilinemultiline^ and $ match at every line
sdotAlldotAll. also matches line breaks
uunicodeunicodeRead the text as code points; strict syntax
vunicodeSetsunicodeSetsEverything u does, plus set operations
ystickystickyMatch only at lastIndex
dhasIndiceshasIndicesReport start and end positions of groups

You can try any combination in the regex tester, which highlights every match in your browser as you type.

g: global

Without g, matching stops at the first match. With it, string methods work on every match:

"a-b-c".replace(/-/, "+");   // "a+b-c"
"a-b-c".replace(/-/g, "+");  // "a+b+c"

"a1b22".match(/\d+/);   // [ '1', index: 1, input: 'a1b22', groups: undefined ]
"a1b22".match(/\d+/g);  // [ '1', '22' ]

Note the different return shapes: without g, match returns one match with its groups and position; with g, it returns only the matched strings. To get every match and its groups, use matchAll, which requires g:

[..."a1b22".matchAll(/(\d+)/g)].map((m) => [m[0], m.index]);
// [ [ '1', 1 ], [ '22', 3 ] ]

"a1b22".matchAll(/\d+/);
// TypeError: String.prototype.matchAll called with a non-global RegExp argument

"a-b".replaceAll(/-/, "+");
// TypeError: String.prototype.replaceAll called with a non-global RegExp argument

split always splits on every match, so g makes no difference there.

The lastIndex trap

A regex with g (or y) is stateful. After a successful test or exec, it stores where the match ended in lastIndex and starts the next search from there:

const re = /a/g;
re.test("a"); // true
re.test("a"); // false: the search started at index 1
re.lastIndex; // 0: reset after the failed search

This bites when one global regex is reused to validate many strings, for example inside array.filter. Drop g when you only need a yes or no, or set re.lastIndex = 0 before each call.

i: ignore case

"cat Cat\ncar".match(/cat/gi); // [ 'cat', 'Cat' ]

Case folding depends on u for some characters. The Kelvin sign (U+212A), which looks like a capital K, folds to the letter k only in Unicode mode:

/\u212A/i.test("k");  // false
/\u212A/iu.test("k"); // true

m vs s: the two flags people mix up

m and s both concern line breaks, but they change different things. m changes the anchors ^ and $; s changes the dot.

m: multiline

const text = "cat Cat\ncar";

text.match(/^ca\w/g);   // [ 'cat' ]          ^ is the start of the whole text
text.match(/^ca\w/gm);  // [ 'cat', 'car' ]   ^ is the start of each line

/t$/.test("cat\ncar");  // false
/t$/m.test("cat\ncar"); // true: "cat" ends a line

gm is the usual pair for processing a file or log line by line.

s: dotAll

By default . matches any character except line terminators. s removes that exception:

/a.b/.test("a\nb");      // false
/a.b/s.test("a\nb");     // true
/a[\s\S]b/.test("a\nb"); // true: the pre-2018 workaround

"<p>one\ntwo</p>".match(/<p>(.*)<\/p>/s)[1]; // "one\ntwo"
"<p>one\ntwo</p>".match(/<p>(.*)<\/p>/m);    // null: m does not help the dot

If a pattern that spans lines returns null, you probably need s, not m.

u: Unicode

JavaScript strings are UTF-16. Without u, a regex sees an emoji as two separate code units; with u, it sees one character:

"๐Ÿ˜€".length;        // 2
/^.$/.test("๐Ÿ˜€");   // false
/^.$/u.test("๐Ÿ˜€");  // true

u is also required for Unicode property escapes. Without it, \p{L} is not a property at all:

/\p{L}/.test("รฉ");  // false
/\p{L}/u.test("รฉ"); // true

Matching words in scripts such as Khmer needs marks as well as letters, because vowel signs and the coeng are combining marks, not letters:

"แžŸแžฝแžŸแŸ’แžแžธ hello".match(/\p{L}+/gu);         // [ 'แžŸ', 'แžŸ', 'แž', 'hello' ]
"แžŸแžฝแžŸแŸ’แžแžธ hello".match(/[\p{L}\p{M}]+/gu);  // [ 'แžŸแžฝแžŸแŸ’แžแžธ', 'hello' ]

Finally, u makes the syntax strict. Escapes that mean nothing are errors instead of being silently ignored:

new RegExp("\\-").test("-"); // true
new RegExp("\\-", "u");      // SyntaxError: Invalid regular expression: /\-/u: Invalid escape

That strictness catches typos, so u is a good default for new patterns.

v: Unicode sets

v is an upgraded u. It adds set operations inside character classes, such as difference (--) and intersection (&&), and properties of strings such as RGI_Emoji, which match multi-code-point emoji:

"abcdef".match(/[\p{L}--[aeiou]]/gv); // [ 'b', 'c', 'd', 'f' ]: letters minus vowels

/^\p{RGI_Emoji}$/v.test("๐Ÿ‘๐Ÿฝ");          // true: thumbs up + skin tone
new RegExp("^\\p{RGI_Emoji}$", "u");    // SyntaxError: ... Invalid property name

new RegExp("a", "uv");                  // SyntaxError: Invalid flags supplied to RegExp constructor 'uv'

u and v cannot be combined; pick one. v was added to browsers in 2023, so use u if you support older ones.

y: sticky

y matches only at exactly lastIndex; it never skips ahead to search. That makes it the right tool for tokenizers, where any unexpected character must be an error rather than silently skipped:

function tokenize(src) {
  const token = /\s*(?:(\d+)|([-+*/()]))/y;
  const out = [];
  while (token.lastIndex < src.length) {
    const start = token.lastIndex;
    const m = token.exec(src);
    if (!m) throw new SyntaxError("Unexpected character at " + start);
    out.push(m[1] ?? m[2]);
  }
  return out;
}

tokenize("12 + 3*(4-1)"); // [ '12', '+', '3', '*', '(', '4', '-', '1', ')' ]
tokenize("12 + x");       // SyntaxError: Unexpected character at 4

Two details matter. A failed sticky match resets lastIndex to 0, which is why the function saves start before calling exec. And the same pattern with g instead of y silently jumps over the x:

[..."12 x 3".matchAll(/\s*(?:(\d+)|([-+*/()]))/g)].map((m) => m[0]);
// [ '12', ' 3' ]: the "x" was skipped without an error

d: match indices

d adds an indices array to each match with the [start, end] position of the whole match and of every group, including named groups. It does not change what matches. Use it to highlight or replace parts of a string by position:

const m = /(?<year>\d{4})-(?<month>\d{2})/d.exec("on 2026-11 ok");

m.indices;        // [ [ 3, 10 ], [ 3, 7 ], [ 8, 10 ], groups: { ... } ]
m.indices.groups; // { year: [ 3, 7 ], month: [ 8, 10 ] }

Reading and setting flags in code

A regex's flags are read-only. The flags property lists them in a fixed order, and each flag also has a boolean property:

/x/gmi.flags; // "gim"

const r = /x/dgimsuy;
r.flags;      // "dgimsuy"
r.global;     // true
r.hasIndices; // true

To change them, build a new regex. Passing a regex to the RegExp constructor with new flags replaces the old ones rather than adding to them:

new RegExp(/x/g, "i").flags; // "i": g is gone
new RegExp(/x/g, "gi").flags; // "gi"

new RegExp("x", "gg"); // SyntaxError: Invalid flags supplied to RegExp constructor 'gg'
new RegExp("x", "x");  // SyntaxError: Invalid flags supplied to RegExp constructor 'x'

The last line matters if you port patterns from other languages: JavaScript has no x (free-spacing) flag.

The same flags in Python

Python has no g: whether you get one match or all of them depends on the function (re.search vs re.findall, and re.sub replaces all unless you pass count). The others map directly. Checked with Python 3.9.6:

import re
text = "cat Cat\ncar"

re.findall(r"cat", text, re.IGNORECASE)    # ['cat', 'Cat']        like /cat/gi
re.findall(r"^ca\w", text, re.MULTILINE)   # ['cat', 'car']        like /^ca\w/gm
re.search(r"a.b", "a\nb", re.DOTALL)       # a match               like /a.b/s
re.sub(r"-", "+", "a-b-c")                 # 'a+b+c'               like replace(/-/g, "+")
re.sub(r"-", "+", "a-b-c", count=1)        # 'a+b-c'               like replace(/-/, "+")

Which flags to use

  • Finding or replacing all matches: g, and use matchAll when you need groups.
  • Validating one value with test: no g, anchored with ^โ€ฆ$.
  • Line-by-line text: gm.
  • A pattern that spans lines: s.
  • Any non-ASCII text or new code: u (or v where supported).
  • A tokenizer or parser: y.

Paste your pattern and sample text into the regex tester to see the effect of each flag. It runs on your browser's own regex engine, so the result matches what your JavaScript code will do, and nothing you type is sent anywhere.

Part of the API debugging series. Start with How to Debug API Responses: A Practical Workflow.