How to Access Regex Matched Groups in JavaScript —match vs exec vs matchAll
Accessing capture groups is one of the most common regex tasks in JavaScript.
You have a pattern with groups:
const regex = /(\d{4})-(\d{2})-(\d{2})/;
const date = "2026-06-13";
You want year, month, day separately.
JavaScript gives you three methods to extract groups: match(), exec(), and matchAll(). Each works differently. Each has surprising edge cases.
Choose the wrong one and you will spend 30 minutes debugging why your groups are null or why only the first match appears.
This guide explains exactly when to use each method, with real production examples.
If you want to test group extraction interactively, the Regex Tester visualizes captured groups clearly.
Method 1: string.match(regex)
The simplest approach.
const date = "2026-06-13";
const regex = /(\d{4})-(\d{2})-(\d{2})/;
const match = date.match(regex);
console.log(match[0]); // "2026-06-13"
console.log(match[1]); // "2026"
console.log(match[2]); // "06"
console.log(match[3]); // "13"
Without the g flag, match() returns an array where:
- index 0 is the full match
- indexes 1+ are captured groups
The g Flag Changes Everything
const text = "2026-06-13 and 2027-01-01";
const regex = /(\d{4})-(\d{2})-(\d{2})/g;
const matches = text.match(regex);
console.log(matches);
// ["2026-06-13", "2027-01-01"]
With the g flag, match() returns only full matches. No groups.
This is one of the most common regex bugs in JavaScript. Developers add the g flag for global matching, then wonder why groups disappeared.
Method 2: regex.exec(string)
exec() returns the first match with groups, regardless of the g flag.
const text = "2026-06-13 and 2027-01-01";
const regex = /(\d{4})-(\d{2})-(\d{2})/g;
const match1 = regex.exec(text);
console.log(match1[0]); // "2026-06-13"
console.log(match1[1]); // "2026"
const match2 = regex.exec(text);
console.log(match2[0]); // "2027-01-01"
console.log(match2[1]); // "2027"
const match3 = regex.exec(text);
console.log(match3); // null
Each call advances regex.lastIndex. When no more matches exist, it returns null.
This is the traditional way to loop through all matches with groups.
The exec() Loop Pattern
const text = "2026-06-13 and 2027-01-01";
const regex = /(\d{4})-(\d{2})-(\d{2})/g;
let match;
const results = [];
while ((match = regex.exec(text)) !== null) {
results.push({
full: match[0],
year: match[1],
month: match[2],
day: match[3]
});
}
console.log(results);
// [
// { full: "2026-06-13", year: "2026", month: "06", day: "13" },
// { full: "2027-01-01", year: "2027", month: "01", day: "01" }
// ]
The while loop is a classic JavaScript regex idiom.
The lastIndex Trap
Since exec() uses mutable lastIndex, state leaks across calls:
const regex = /\d+/g;
console.log(regex.exec("abc 123 def")); // ["123"]
console.log(regex.exec("abc 123 def")); // null —lastIndex is stuck
If you reuse the same regex object, lastIndex does not reset automatically.
Fix:
regex.lastIndex = 0; // Reset before reuse
Or create a new regex object each time.
Related reading: Regex Works in Regex101 but Not in JavaScript —Why
Method 3: string.matchAll(regex)
Introduced in ES2020, matchAll() returns an iterator of all matches WITH groups.
const text = "2026-06-13 and 2027-01-01";
const regex = /(\d{4})-(\d{2})-(\d{2})/g;
const matches = text.matchAll(regex);
for (const match of matches) {
console.log(match[0]); // full match
console.log(match[1]); // year
console.log(match[2]); // month
console.log(match[3]); // day
}
matchAll() combines the simplicity of match() with the group access of exec().
matchAll Requires the g Flag
const regex = /(\d{4})-(\d{2})-(\d{2})/; // No g flag
const text = "2026-06-13";
const matches = text.matchAll(regex);
// TypeError: String.prototype.matchAll called with a non-global RegExp
Without the g flag, matchAll() throws a TypeError.
matchAll Returns an Iterator, Not an Array
const regex = /(\d)/g;
const text = "123";
const matches = text.matchAll(regex);
console.log(Array.isArray(matches)); // false
console.log([...matches].length); // 3
To get an array, spread or use Array.from():
const results = [...text.matchAll(regex)];
// or
const results = Array.from(text.matchAll(regex));
Named Capture Groups
All three methods support named groups, introduced in ES2018.
const regex = /(?<year>\d{4})-(?<month>\d{2})-(?<day>\d{2})/;
const date = "2026-06-13";
With match():
const match = date.match(regex);
console.log(match.groups.year); // "2026"
console.log(match.groups.month); // "06"
With exec():
const match = regex.exec(date);
console.log(match.groups.year); // "2026"
With matchAll():
const matches = date.matchAll(/(?<year>\d{4})-(?<month>\d{2})-(?<day>\d{2})/g);
for (const match of matches) {
console.log(match.groups.year);
}
Real Example: Parsing Log Lines
const logLine = "2026-06-13 14:30:00 ERROR [auth] User login failed";
const regex = /(?<date>\d{4}-\d{2}-\d{2})\s+(?<time>\d{2}:\d{2}:\d{2})\s+(?<level>\w+)\s+\[(?<module>\w+)\]\s+(?<message>.+)/;
const match = logLine.match(regex);
if (match) {
console.log(match.groups);
// {
// date: "2026-06-13",
// time: "14:30:00",
// level: "ERROR",
// module: "auth",
// message: "User login failed"
// }
}
Named groups make log parsing dramatically more readable.
Real Example: Extracting Multiple Values
const text = "Price: $50.00, Tax: $5.00, Total: $55.00";
const regex = /(?<label>\w+):\s+\$(?<amount>\d+\.\d{2})/g;
for (const match of text.matchAll(regex)) {
console.log(`${match.groups.label}: ${match.groups.amount}`);
}
// Price: 50.00
// Tax: 5.00
// Total: 55.00
Comparison Table
| Feature | match() | exec() | matchAll() |
|---|---|---|---|
Returns groups without g | Yes | Yes | Error |
Returns groups with g | No | Yes | Yes |
| Returns all matches at once | Yes (no groups) | No (loop) | Yes (iterator) |
| Supports named groups | Yes | Yes | Yes |
| ES version | ES3 | ES3 | ES2020 |
| Mutates regex state | No | Yes (lastIndex) | No |
When to Use Each
Use match() when:
- you want a quick single match
- you do not need groups (with
gflag) - you need to check if a match exists
const hasMatch = /^\d+$/.test(input); // Or just .test()
Use exec() when:
- you need backward compatibility
- you want a looping pattern without spread overhead
- you need precise control over match position
Use matchAll() when:
- you need all matches with groups
- you can target ES2020+ environments
- you want cleaner code than
exec()loops
Common Mistake: match() with g Flag Expecting Groups
const text = "foo123 bar456";
const regex = /([a-z]+)(\d+)/g;
const matches = text.match(regex);
console.log(matches);
// ["foo123", "bar456"] —no groups!
This is the most common group access bug in JavaScript.
Fix: use matchAll() or exec().
Common Mistake: Mutating the Same Regex Object
const regex = /\d+/g;
["abc 123", "def 456"].forEach(str => {
const match = regex.exec(str);
// Second iteration: regex.lastIndex is non-zero from first call
// Match may be null or incorrect
});
Fix: reset lastIndex or create a new regex per iteration.
Common Mistake: Forgetting g with matchAll()
const regex = /(\d+)/; // Missing g
const text = "123 456";
const matches = text.matchAll(regex);
// TypeError thrown
Related reading: JavaScript Regex Flags Explained —g, i, m, s, u, y
regex.test() vs Groups
Sometimes you only need groups if a match exists:
const regex = /(\d{4})-(\d{2})-(\d{2})/;
const date = "2026-06-13";
if (regex.test(date)) {
const match = date.match(regex); // Or regex.exec(date)
console.log(match[1]); // "2026"
}
But this runs the regex twice. Better:
const match = regex.exec(date);
if (match) {
console.log(match[1]); // "2026"
}
Related reading: Check Whether a String Matches a Regex in JS —test() vs exec() vs match()
FAQ
How do I get regex capture groups in JavaScript?
Use match(), exec(), or matchAll(). Without the g flag, match() returns groups. With g, use matchAll() or exec().
What does string.match(regex) return?
Without g: array with full match at index 0 and groups at 1+.
With g: array of full matches only (no groups).
What does regex.exec(string) return?
An array with the full match and groups, plus index and input properties. Advances lastIndex when global.
What does string.matchAll(regex) return?
An iterator yielding match objects (with groups) for each match. Requires the g flag.
Why does match() not return groups with the g flag?
When the g flag is set, match() returns all full matches as a flat array. Group information is discarded for simplicity.
How do I use named capture groups?
Use (?<name>...) syntax, then access via match.groups.name or match[1].
What is the lastIndex property?
A property on global regex objects that tracks where the next exec() search starts. It can cause state-related bugs.
Final Thoughts
Three methods. Same goal. Different behaviors.
The key rule for JavaScript regex groups:
- No
gflag? Usematch()—it returns groups directly. - With
gflag? UsematchAll()—it returns groups and all matches. - Need IE support? Use
exec()in a loop.
The match() + g flag trap catches everyone at least once. After that, muscle memory kicks in and you reach for matchAll() by default.
If you want to see exactly how each method handles groups, the Regex Tester visualizes all captured groups for any pattern.
You may also find these related developer tools useful: