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

Featurematch()exec()matchAll()
Returns groups without gYesYesError
Returns groups with gNoYesYes
Returns all matches at onceYes (no groups)No (loop)Yes (iterator)
Supports named groupsYesYesYes
ES versionES3ES3ES2020
Mutates regex stateNoYes (lastIndex)No

When to Use Each

Use match() when:

  • you want a quick single match
  • you do not need groups (with g flag)
  • 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 g flag? Use match() —it returns groups directly.
  • With g flag? Use matchAll() —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: