How to Use Named Capture Groups in Regex With Examples
Use named capture groups in regex: syntax in JavaScript, Python, PCRE and .NET, referring back by name, replacing with names and what grep, sed and awk lack.

A regex with five groups and the line $4 in the next function is a puzzle for the next reader, and an inserted group silently renumbers everything. Naming each group fixes both. Syntax differs enough between languages to deserve a table, and this post collects it with examples. It extends backreferences and capture groups.
The syntax by language
Language Define Backreference Read the value
JavaScript (?<y>\d{4}) \k<y> m.groups.y
Python (?P<y>\d{4}) (?P=y) m.group("y")
PCRE / PHP (?<y>\d{4}) \k<y> or (?P=y) $m['y']
.NET / C# (?<y>\d{4}) \k<y> m.Groups["y"]
Java (?<y>\d{4}) \k<y> m.group("y")
Go (RE2) (?P<y>\d{4}) not supported re.SubexpNames()
Go's regexp (RE2) uses the (?P form and, like all RE2 engines, has no backreferences by design. Group names must start with a letter and contain letters, digits and underscores. A name can usually be used only once in a pattern, with a few engines allowing duplicates in alternations.
Python
import re
pattern = re.compile(r"(?P<year>\d{4})-(?P<month>\d{2})-(?P<day>\d{2})")
m = pattern.search("due 2026-11-03")
print(m.group("year"), m.groupdict())
print(pattern.sub(r"\g<day>/\g<month>/\g<year>", "2026-11-03"))2026 {'year': '2026', 'month': '11', 'day': '03'}
03/11/2026
groupdict() returns every named group as a dictionary, which is handy for building records straight from log lines. In a replacement string \g<name> refers to a group, avoiding the ambiguity of \10. The patterns pair well with the verbose mode in readable regex with comments.
JavaScript and PHP
const re = /(?<user>[^@]+)@(?<host>.+)/;
const { user, host } = "[email protected]".match(re).groups;
console.log(user, host);ada example.test
Destructuring groups turns a match into variables, which reads far better than m[1] and m[2]. In PHP, named groups appear in the match array under both the name and the number, so $m['user'] works. The full tour for those is in JavaScript regex and PHP preg functions.
Back to the same text
/(?<q>["']).*?\k<q>/.exec(`say "hi" or 'bye'`)[0]; // "hi"A backreference by name matches the same text the group captured. Here it finds a quoted string with matching opening and closing quotes. Named references stay valid when you add groups before it, where \2 would not.
Shell tools
echo "2026-11-03" | grep -oP '(?<y>\d{4})-(?<m>\d{2})'
echo "2026-11-03" | perl -pe 's/(?<y>\d{4})-(?<m>\d{2})-(?<d>\d{2})/$+{d}\/$+{m}\/$+{y}/'2026-11
03/11/2026
grep -P (GNU only) accepts the syntax but cannot output a group by name. sed and awk have only numbered groups (\1 in sed; gawk's three-argument match), so use Perl or Python when you need names in a shell pipeline. macOS grep lacks -P.
Readability is the point
A regular expression with six numbered groups forces the reader to count parentheses. A pattern with named groups documents itself: (?P<year>\d{4})-(?P<month>\d{2}) says what each piece means, and code that reads match["month"] survives a later edit that inserts a group at the front. With numbers, inserting a group renumbers everything after it, and every index in the calling code silently points at the wrong text. Named groups turn that class of bug into an error about a missing name.
The cost is a little verbosity in the pattern. For one-off command line work, numbered groups are fine. For anything stored in a file, reviewed or revisited, the names pay for themselves almost immediately.
Duplicate names and optional groups
Two behaviours trip people up. First, an optional group that did not participate in the match returns None in Python and undefined in JavaScript, not an empty string, so calling a string method on it raises. Check for it, or give the group a default with or "". Second, using the same name twice is an error in most engines: Python's re rejects it, while PCRE accepts it only with the (?J) flag or inside a branch reset group. The newer regex module in Python and recent JavaScript engines, which allow the same name in different alternatives, can express "either format" without extra code.
Group names have their own rules. They must be valid identifiers in Python, may not start with a digit, and in JavaScript they are limited to letters, digits, underscores and the dollar sign. Pick lowercase words joined by underscores and the pattern is portable between engines apart from the opening syntax.
Using names in replacements
Names are useful when rewriting text as well as extracting it. In Python, re.sub accepts \g<month> in the replacement, and JavaScript uses $<month>. A date reordering such as turning year-month-day into day/month/year becomes a readable one-line substitution. Because the replacement refers to meaning and not position, it stays correct when the pattern grows.
Named groups also make backreferences within the pattern clearer. (?P<q>["']).*?(?P=q) matches a quoted string with matching quote characters, and the reader can see that the closing quote repeats the opening one. The numbered form \1 works but gives no hint of intent.
Testing and structure for larger patterns
Treat a significant pattern as code. Keep a small table of sample inputs and the expected value of each group, and run it as a unit test. Cases to include are the happy path, a line that should not match, a line with an optional part absent and a line with unusual whitespace. A test of this kind catches the change that quietly alters which text a group captures.
Combine named groups with the verbose flag, re.VERBOSE in Python, so each part sits on its own line with a comment, as described in commenting regular expressions. Beware of catastrophic backtracking on nested quantifiers, covered in regex backtracking; names do not change performance, only readability, so the same care applies to the pattern's structure.
In shell tools, named groups are less widely supported. GNU grep with -P uses PCRE and accepts (?<name>...), but cannot print a named group directly; use -o with lookbehind or a small Python filter. The tools sed and awk have no names at all, so keep them to numbered groups there. When a pattern needs to be shared across languages, keep the syntax to the common subset: the angle bracket form (?<name>...) works in Python 3.12+, JavaScript, PHP, Java, .NET and PCRE, which makes it the safest spelling today.
Document the group names near the pattern, and prefer names that match the field names of the data the code builds, such as the keys of a dictionary. When pattern and data model share vocabulary, the glue code gets shorter.
One more benefit shows up in code review. A reviewer who sees (?P<user>[^@]+)@(?P<domain>.+) can judge the pattern against the field names without decoding it, and can see at once whether the code that consumes the match uses the right ones. Reviews of anonymous groups tend to approve the pattern on trust, because verifying them means counting parentheses by eye. A small naming habit therefore improves correctness as well as style.
Prefer non-capturing groups, (?:...), for grouping that does not need to be read back. Every capture costs a little time and clutters the result with values nobody wants, and removing the unneeded ones makes the named groups stand out as the intended outputs of the pattern.
Where this does not apply
Names do not make a long regex easy to read; if the pattern needs more than four groups, a small parser or several steps may be clearer. Do not rely on duplicate names across alternatives unless the documentation of your engine allows it. See the MDN reference for named groups and the Python re syntax reference.


