DevRivet

Blog ·

JavaScript Regex Named Groups: Syntax, Access and Replace

Name a JavaScript regex capture group with (?<name>...), read it from match.groups, and reference it with \k<name> or $<name>. Examples and pitfalls.

Regex Tester

Test JavaScript regular expressions with match highlighting and capture groups.

Try it free →

A named capture group gives a regex group a name, like (?<year>\d{4}), so you can read the match as match.groups.year instead of counting positions with match[1]. The syntax is (?<name>pattern). It works in regex literals and in new RegExp() strings, and it is supported in all current evergreen browsers and in Node.js.

To try a pattern against sample input while you write it, paste it into the Regex Tester. It highlights matches and shows each capture group, which makes it easy to confirm that a name is attached to the part of the string you expect.

Basic syntax and match.groups

const re = /(?<year>\d{4})-(?<month>\d{2})-(?<day>\d{2})/;
const match = 'Released 2024-03-09'.match(re);

console.log(match.groups.year);  // '2024'
console.log(match.groups);       // [Object: null prototype] { year: '2024', month: '03', day: '09' }
console.log(match[1]);           // '2024'

The groups object is created with a null prototype. It has no inherited methods such as hasOwnProperty or toString, so use Object.hasOwn(match.groups, 'year') or check for undefined directly when you need to test for a group.

Named groups are still numbered. match[1] returns the same value as match.groups.year, so existing code that uses numeric indexes keeps working if you add names later.

Referencing a group inside the pattern

Use \k<name> to match the same text that an earlier named group matched. This is the named form of a backreference. It is easier to read than \1 once a pattern has several groups.

const quoted = /(?<q>['"]).*?\k<q>/;

quoted.test('say "hi"');  // true
quoted.test("it's");      // false: no closing quote
quoted.test("'a\"");     // false: opening ' does not match closing "

A backreference to a group that has not matched yet does not cause a failure. In JavaScript, \k<name> matches the empty string in two cases: when the named group appears later in the pattern (a forward reference), and when the group exists but did not participate in the match.

/\k<word>(?<word>\w+)/.exec('abc');
// ['abc', 'abc']  forward reference matched '' and the group then matched 'abc'

/(?<a>x)?\k<a>y/.exec('y');
// ['y', undefined]  group a did not participate, so \k<a> matched ''

Both cases are usually bugs. Keep each group before its \k<name> reference so the intent is clear, and make sure the group is required if the reference is meant to match real text.

Using named groups in replace()

In a replacement string, $<name> inserts the text captured by that group. This lets you reorder dates or rewrite fields without touching the numeric positions.

'2024-03-09'.replace(
  /(?<y>\d{4})-(?<m>\d{2})-(?<d>\d{2})/,
  '$<d>/$<m>/$<y>'
);
// '09/03/2024'

If the pattern has no named groups, $<name> is not special. The text $<name> stays in the output as literal characters. This is a common source of confusion when a regex loses its names during an edit.

For more logic, pass a function. Its first argument is the full match, followed by one argument per numbered group. Named groups are passed as positional arguments too. After the groups come the offset of the match and the full input string. When the pattern has named groups, an object with those groups is appended as the last argument. The example below uses only the first four parameters, so the offset, the input string and the groups object are ignored.

const out = 'Due 2024-03-09'.replace(
  /(?<y>\d{4})-(?<m>\d{2})-(?<d>\d{2})/,
  (_, y, m, d) => `${d}.${m}.${y}`
);
// 'Due 09.03.2024'

Iterating over every match with matchAll

String.prototype.matchAll returns an iterator of match objects, and each one has its own groups object. The regex must have the g flag, or matchAll throws a TypeError.

const log = 'a=1 b=22 c=333';

for (const m of log.matchAll(/(?<key>\w+)=(?<value>\d+)/g)) {
  const { key, value } = m.groups;
  console.log(key, value);
}
// a 1
// b 22
// c 333

Optional groups return undefined

A named group that did not participate in the match has the value undefined, not an empty string. This matters when you concatenate values or compare them.

const re = /(?<prefix>[a-z]+)?:(?<id>\d+)/;
const m = re.exec(':42');

m.groups;               // [Object: null prototype] { prefix: undefined, id: '42' }
const prefix = m.groups.prefix ?? '';  // ''

Pitfalls that cause real bugs

  • Names must be valid identifiers: letters, digits, $ and _, and the name cannot start with a digit. A name like (?<first-name>) or (?<2fa>) is a SyntaxError when the regex is created.
  • Names are case-sensitive. match.groups.Year is undefined if the group is named year.
  • match.groups is undefined when the regex has no named groups. Reading match.groups.year then throws a TypeError. Check for a match first, and check that the pattern really contains named groups.
  • When you destructure, a group name can collide with a variable you already have. Rename it in the destructuring pattern, for example: const { year: y } = match.groups;
  • Do not rely on reusing the same group name twice in one pattern. Support for duplicate names differs across engines and versions, so pick unique names.
  • Other languages use different syntax. Python's re module uses (?P<name>...) and match.group('name'). A pattern copied from Python will not run in JavaScript without changes.

Getting group positions

If you need the start and end index of a named group, add the d flag. The match then has an indices property, and indices.groups holds a [start, end] pair for each named group.

const m = /(?<year>\d{4})/d.exec('in 2024');

m.indices.groups.year;  // [3, 7]

Validating real input

Named groups are most useful when a pattern has several fields, such as a phone number or an address. The phone number patterns guide shows how groups split regional formats, and the email validation article explains why a single regex has limits for that kind of input. Test each pattern against both valid and invalid samples before you rely on it.

FAQ

Can I combine named groups with flags like i, s or u?

Yes. Flags such as g, i, m, s and u do not change the group syntax. You can use them together with named groups in the same way as numbered groups.

Do named groups make a regex slower?

A named group is still a capture group, and the name is only metadata for lookup. In practice, the difference is negligible for most code. If performance matters, measure your actual input rather than relying on general rules.

Why does match.groups show [Object: null prototype]?

The object is created without a prototype on purpose, so a group named toString or constructor cannot collide with built-in properties. Read the values with normal property access, such as match.groups.year.

Regex Tester

Test JavaScript regular expressions with match highlighting and capture groups.

Try it free →