String.prototype.matchAll()

Returns an iterator of every regex match, each with its capture groups and index.

Signature
matchAll(regexp)
  • Added in JavaScript ES2020

Parameters

ParameterTypeDescription
$regexpRegExp | { [Symbol.matchAll](): any }A regular expression object, or any object with a Symbol.matchAll method. A value that is neither is implicitly converted with new RegExp(regexp, 'g'). A regex argument must have the global (g) flag set.

Return value

RegExpStringIteratorAn iterable iterator object (which is not restartable) of matches, or an empty iterator if no matches are found. Each yielded value is an array with the same shape as the return value of RegExp.prototype.exec().

Errors

Throws a TypeError if regexp is a regex whose flags do not contain "g".

Changelog

  • PHP ES2020Introduced, replacing the while (m = re.exec(str)) loop and its lastIndex bookkeeping.

matchAll() is the answer to the one thing match() cannot do: give you every match and its capture groups. Each yielded value is a full exec()-style result — [0] the whole match, [1..n] the groups, plus index, input and groups.

The g flag is mandatory

A regex without g throws a TypeError immediately. Without the flag there is no sensible "all matches" semantics, so the specification refuses rather than guessing.

It returns an iterator, not an array

You cannot index into it or read .length. Either for...of it, or spread it into an array with [...str.matchAll(re)]. The iterator is not restartable — once consumed it yields nothing, so if you need two passes, materialise it into an array first.

Why it beats a manual exec loop

The old idiom, while ((m = re.exec(s)) !== null), mutates the regex's lastIndex. Sharing that regex elsewhere, or forgetting to handle a zero-length match, causes infinite loops and phantom bugs. matchAll() internally clones the regex, so your original object's lastIndex is left alone.

Example

Try it yourself
Loading editor…
Press Run to execute the code.

When to use it

  • Extract every key/value pair from a query string with groups
  • Find all markdown links and their positions in a document
  • Collect every timestamp in a log with its offset
  • Tokenise an expression while keeping each token's index

Frequently asked questions

Why does matchAll throw a TypeError?

Because the regular expression is missing the global g flag, which matchAll() requires by specification. Change /foo/ to /foo/g; there is no non-global meaning for an all-matches iterator.

How do I turn matchAll results into an array?

Spread the iterator with [...str.matchAll(re)] or use Array.from(str.matchAll(re)). The iterator itself has no length property and cannot be indexed, and it can only be walked once.

When should I use matchAll instead of match?

Use matchAll() whenever you need capture groups, the index, or the groups object for every match, since match() with the g flag discards all of those and returns only the matched strings. Use match() without g when one match is enough.

More examples

Parsing key/value pairs with named groups

The output is { page: "2", sort: "name", dir: "desc" }. Because every match carries its own groups object, one pass turns the query string into a plain object — something match() with /g/ cannot do since it strips the groups.

Example · javascript
const qs = "page=2&sort=name&dir=desc";
const re = /(?<key>\w+)=(?<value>[^&]+)/g;

const params = Object.fromEntries(
  [...qs.matchAll(re)].map((m) => [m.groups.key, m.groups.value])
);

console.log(params);
console.log(params.sort);

The iterator is single-use

The second spread yields 0 because the iterator was exhausted by the first. Storing the spread result in a variable gives 2 and 2, which is why any code that both counts and loops must materialise the array up front.

Example · javascript
const it = "x1 x2".matchAll(/x(\d)/g);

console.log([...it].length); // 2
console.log([...it].length); // 0 — already consumed

// Materialise once, reuse freely
const all = [..."x1 x2".matchAll(/x(\d)/g)];
console.log(all.length, all.length);

Replacing the exec loop

Each match destructures straight into full, user and host. The final 0 is the important part: matchAll clones the regex internally, so the original object's lastIndex is never touched. A hand-written exec loop mutates lastIndex as it goes — it only returns to 0 if you run the loop until exec() returns null, so breaking out early leaves it parked at the end of the last match and reusing that same regex object elsewhere silently starts matching from the middle of the next string.

Example · javascript
const re = /(\w+)@(\w+)\.com/g;
const text = "[email protected] and [email protected]";

// matchAll leaves the original regex's lastIndex alone
for (const [full, user, host] of text.matchAll(re)) {
  console.log(full, user, host);
}
console.log(re.lastIndex); // 0

Discussion

  • Be the first to comment on this lesson.