Numbered capturing groups work, but match[1], match[2], match[3] don’t tell you anything about what they contain — you have to go read the pattern to remember which number is the year and which is the day. Named groups fix this by letting you label a capture directly in the pattern.
The syntax
A named group looks like a normal capturing group with ?<name> right after the opening parenthesis:
/(?<year>\d{4})-(?<month>\d{2})-(?<day>\d{2})/
The group still captures exactly like (\d{4}) would — the only difference is that you can retrieve it by name instead of by position, through the match result’s .groups object:
const match = "2026-07-10".match(/(?<year>\d{4})-(?<month>\d{2})-(?<day>\d{2})/);
console.log(match.groups.year); // "2026"
console.log(match.groups.month); // "07"
console.log(match.groups.day); // "10"
Tip
The Demo above highlights matches the same way every other demo does — this simplified tester doesn’t render a .groups breakdown inline. To see the actual named-group values, open the pattern in the full Playground (the link above the demo) or run the code snippet yourself.
Named groups in replacements and backreferences
Just like numbered groups have $1 for replacements and \1 for backreferences, named groups have their own equivalents: $<name> and \k<name>.
const iso = "2026-07-10";
const us = iso.replace(
/(?<year>\d{4})-(?<month>\d{2})-(?<day>\d{2})/,
"$<month>/$<day>/$<year>"
);
console.log(us); // "07/10/2026"
Why this matters for complex patterns
Named groups earn their keep most in patterns that parse structured, multi-part text — the kind where numbered groups turn into a guessing game. Compare parsing a log line both ways:
// Numbered — which is the level, which is the message?
/(ERROR|WARN|INFO): (.+)/
// Named — self-documenting, no guessing required
/(?<level>ERROR|WARN|INFO): (?<message>.+)/
const line = "ERROR: disk full";
const match = line.match(/(?<level>ERROR|WARN|INFO): (?<message>.+)/);
console.log(match.groups.level); // "ERROR"
console.log(match.groups.message); // "disk full"
Notice the DEBUG line isn’t matched at all — the alternation only lists ERROR, WARN, and INFO, so anything else is correctly filtered out.
Numbered vs. named, at a glance
Numbered (...) |
Named (?<name>...) |
|
|---|---|---|
| Access in JS | match[1], match[2] |
match.groups.name |
| Backreference | \1 |
\k<name> |
| Replacement | $1 |
$<name> |
| Readable without checking the pattern | No | Yes |
| Breaks if group order changes | Yes | No |
Parse a date into named parts
Write a pattern with named groups year, month, and day that matches a date like 2026-07-10, then use match.groups to log each part on its own line.
Show solution
const match = "2026-07-10".match(/(?<year>\d{4})-(?<month>\d{2})-(?<day>\d{2})/);
console.log(`Year: ${match.groups.year}`);
console.log(`Month: ${match.groups.month}`);
console.log(`Day: ${match.groups.day}`);Because each group is named, anyone reading this code — including you, months from now — can tell exactly what each value represents without cross-referencing the pattern.
What’s next
So far every pattern has matched one option or a fixed shape. Next up: alternation — using | to match one of several alternatives, and the precedence gotchas that come with it.