Regex Flags Explained: g, i, m, s, u, v, y, d and Combinations Like /gm and /gim

Flags change how a whole pattern is interpreted without changing the pattern itself. The same `^\d+$` matches one line or every line, and the same `.` does or does not cross a newline, depending on a single letter after the closing slash. Most "my regex works in the tester but not in my code" reports come down to a flag that is on in one place and off in the other.

The flags at a glance

JavaScript currently defines eight flags. They go after the closing delimiter, /pattern/gi, or in the second argument of new RegExp('pattern', 'gi'). Order does not matter, and the flags property always reports them in alphabetical order.

flag  property     introduced   effect
g     global       ES3          find every match, not just the first
i     ignoreCase   ES3          case-insensitive matching
m     multiline    ES3          ^ and $ match at line breaks
s     dotAll       ES2018       . also matches line terminators
u     unicode      ES2015       treat the pattern as Unicode code points
y     sticky       ES2015       match only at lastIndex
d     hasIndices   ES2022       report start/end indices of groups
v     unicodeSets  ES2024       u plus set operations and string properties

Flags cannot be added to an existing regex. flags is read-only, so create a new one with new RegExp(re.source, re.flags + 'i').

g: global, and the lastIndex trap

Without g, a regex describes one match. With it, String.prototype.replace replaces every occurrence, match returns all matches rather than the first match with groups, and matchAll is allowed at all. matchAll and replaceAll throw a TypeError if a regex lacks g.

The trap is that a global regex is stateful. test() and exec() start searching at lastIndex and update it after each match. Reusing one global regex for several tests therefore gives alternating results:

const isDigits = /^\d+$/g;
isDigits.test('123');   // true   (lastIndex is now 3)
isDigits.test('123');   // false  (search starts at index 3)
isDigits.test('123');   // true   (lastIndex was reset to 0 by the failure)

// Fix: drop g when you only need a yes/no answer
const isDigitsOk = /^\d+$/;

It is hard to spot because the pattern is correct and the failure is intermittent: the first check passes, the second fails with identical input. For validation you almost never want g.

i: ignore case

i makes letters match regardless of case. Without u or v, JavaScript only applies simple case folding and ignores some Unicode rules. With u, case folding follows Unicode, so /\u212A/iu (KELVIN SIGN) matches k, and /ſ/iu (long s) matches s. That is usually what you want, but it can surprise validation code that assumes ASCII.

Case-insensitive matching of user input against a fixed list is safer done on normalised strings, a.localeCompare(b, undefined, { sensitivity: 'accent' }) === 0, than with a regex built from untrusted input.

m versus s: the two flags everyone mixes up

Both flags concern newlines, but they change different tokens. m changes the anchors ^ and $. s changes the dot.

const text = 'first line\nsecond line';

/^second/.test(text);        // false: ^ means start of the whole string
/^second/m.test(text);       // true:  ^ also matches after \n

/first.*line$/.test(text);   // true:  matches within line 1 only
/first.+second/.test(text);  // false: . does not match \n
/first.+second/s.test(text); // true:  with s, . matches \n too

A validation pattern such as /^\d{5}$/m is a real bug. With m, the input 12345\n<script> passes, because $ matches before the newline on the first line. Leave m off when you validate a whole field.

Before the s flag existed, the usual workaround for "any character including newline" was [\s\S] or [^]. You will still see these in older code, and they behave the same as . with s.

u and v: Unicode mode

Without u, JavaScript regexes work on UTF-16 code units. An emoji or any character outside the Basic Multilingual Plane is two units, so . matches half of it and {1} counts it as two.

/^.$/.test('😀');         // false: two code units
/^.$/u.test('😀');        // true

/\p{L}+/u.test('Łódź');   // true: Unicode property escape (needs u or v)
/[\p{L}--[a-z]]/v;        // v: set subtraction, any letter except a-z

u also makes the syntax strict. An unnecessary escape such as \- outside a class, or \a, becomes a SyntaxError instead of being silently ignored. That catches real mistakes. It also means a pattern copied from another language may stop compiling once you add u.

v, added in ES2024, is a superset of u. It adds set operations inside character classes (-- for subtraction, && for intersection) and properties of strings such as \p{RGI_Emoji}. You cannot combine u and v: /x/uv is a syntax error. For new code that needs Unicode awareness, v is the modern choice where your runtimes support it.

y and d: sticky matching and match indices

y anchors each match at lastIndex. The regex either matches exactly there or fails, rather than searching ahead. Tokenisers use it to consume input piece by piece without skipping unexpected characters.

const num = /\d+/y;
num.lastIndex = 3;
num.exec('abc123')[0];   // '123': the digits start exactly at index 3
num.lastIndex = 2;
num.exec('abc123');      // null: index 2 is 'c', and y never searches ahead

const re = /(?<year>\d{4})-(?<month>\d{2})/d;
const m = re.exec('Due 2026-09');
m.indices.groups.year;   // [4, 8]: start and end of the named group

d fills match.indices with the start and end offsets of every group. Editors and highlighters need them, and before ES2022 they had to be reconstructed by hand. It adds overhead, so enable it only when you read the indices.

The same flags in Python, PHP and other engines

Most engines share the concepts but spell them differently. A pattern that works in an online tester set to JavaScript can behave differently in your backend because the defaults differ.

concept            JavaScript   Python            PHP (PCRE)   inline
ignore case        i            re.IGNORECASE     i            (?i)
^/$ at lines       m            re.MULTILINE      m            (?m)
dot matches \n     s            re.DOTALL         s            (?s)
verbose/comments   (none)       re.VERBOSE        x            (?x)
Unicode            u / v        default for str   u            —
all matches        g            re.findall/sub    preg_match_all  —
import re
re.findall(r'^\w+', text, flags=re.MULTILINE | re.IGNORECASE)

# Inline flags must come first in Python 3.11+, or re.compile raises an error
re.compile(r'(?i)hello')      # fine
re.compile(r'hello(?i)')      # re.error: global flags not at the start
<?php
preg_match_all('/^\w+/mi', $text, $m);
preg_match('/^\p{L}+$/u', $name);   // without u, PCRE sees bytes, not UTF-8 characters

Python has no global flag: you choose re.search, re.findall or re.sub instead. In PHP, u matters even when you do not use \p{…}. Without it, . matches a single byte, so a multi-byte UTF-8 character can be split in half.

Sources and further reading

MDN documents each JavaScript flag and the RegExp.prototype.flags property; the ECMAScript specification defines them normatively. The Python documentation for the re module lists its flags and the inline-flag rules that changed in 3.11. The PHP manual page "Pattern Modifiers" covers PCRE's i, m, s, x and u.

Frequently asked questions

What does the g flag do in regex?

It makes the regex global: `replace` changes every match and `match` returns all of them. It also makes the regex stateful through `lastIndex`, so calling `test()` repeatedly on the same global regex can alternate between true and false.

What is the difference between the m and s flags?

`m` (multiline) changes `^` and `$` so they match at the start and end of each line. `s` (dotAll) changes `.` so it also matches newline characters. They are independent and are often used together.

Why does my regex test() return true and then false?

The regex has the `g` or `y` flag, so `test()` continues from `lastIndex` after each match. Remove the flag for yes/no checks, or reset `re.lastIndex = 0` before each call.

When should I use the u flag?

Whenever the input can contain non-ASCII text, emoji or characters outside the Basic Multilingual Plane, and whenever you use `\p{…}` property escapes. It also makes the pattern syntax strict, which catches invalid escapes.

How do I set regex flags in Python?

Pass them as the `flags` argument, for example `re.search(p, s, re.I | re.M)`, or write inline flags such as `(?i)` at the very start of the pattern. There is no global flag; use `findall`, `finditer` or `sub` to process every match.

Can I combine the u and v flags?

No. `v` already includes everything `u` does, and using both is a syntax error. Choose `v` where your runtime supports ES2024, and `u` otherwise.