A regex pattern describes what to match; flags describe how to apply it — case sensitivity, whether to find one match or all of them, how anchors and . behave, and more. Flags go after the closing slash in a literal (/pattern/flags) or as a second argument to the RegExp constructor.
const re1 = /hello/gi;
const re2 = new RegExp("hello", "gi");
re1.test("Hello world"); // true
re2.source; // "hello"
re2.flags; // "gi"
Both forms are equivalent — use the literal syntax when the pattern is known upfront, and new RegExp(...) when you need to build a pattern dynamically (for example, from user input).
The flags, one by one
| Flag | Name | Effect |
|---|---|---|
g |
global | Find all matches instead of stopping at the first |
i |
ignoreCase | Match letters regardless of case |
m |
multiline | ^ and $ match at the start/end of each line |
s |
dotAll | . also matches newline characters |
u |
unicode | Treats the pattern as Unicode code points; enables \u{...} and \p{...} |
y |
sticky | Match must start exactly at lastIndex, no scanning ahead |
d |
hasIndices | Adds a .indices array with start/end positions of each match and group |
v |
unicodeSets | Newer (ES2024) superset of u supporting set operations inside character classes |
Combining flags
Flags are just letters — order doesn’t matter, and you can combine as many as make sense together:
/^error/gim.test("Warning\nERROR: disk full"); // true, thanks to m + i
Here m lets ^ match at the start of every line, not just the first, and i lets Cat match the pattern cat regardless of case. Try removing m or i in the demo above and watch matches disappear.
dotAll in action
Normally, . refuses to match line-terminator characters like \n. The s flag removes that restriction:
/a.b/.test("a\nb"); // false — . doesn't match \n by default
/a.b/s.test("a\nb"); // true — s makes . match anything, including \n
Sticky vs. global
g and y both aim to find repeated matches, but they behave differently: g’s .exec() calls search forward from lastIndex until a match is found anywhere in the remaining string, while y demands the match start exactly at lastIndex — no skipping ahead. Sticky matching is what tokenizers and parsers typically use, since they need to know the match happened right where they expected it.
Tip
The d flag doesn’t change what matches — it just adds extra information (.indices) to the result, telling you the exact start/end character positions of the whole match and each capture group. It’s handy for building things like syntax highlighters that need to know where a match sits, not just what it is.
Predict the flag combination
Given const re = /^\s*todo/im; and the text "Notes\n TODO: ship it", will re.test(...) return true or false? Which two flags are doing the work?
Show solution
It returns true. The m flag lets ^ match at the start of the second line (not just the very beginning of the string), and the i flag lets TODO match the lowercase todo in the pattern. Without m, ^ would only match before "Notes", and the leading whitespace plus TODO on the second line would never be reached.
What’s next
Flags like u unlock a whole category of patterns that go beyond plain ASCII. Next, we’ll dig into Unicode matching — \p{...} property escapes, and why \w alone quietly fails on accented and non-Latin text.