Back to Blog
JavaScriptRegExpNamed GroupsPattern Matching

JavaScript RegExp Named Groups Guide

Master JavaScript regular expression named capture groups for readable and maintainable pattern matching.

B
Bootspring Team
Engineering
September 28, 2019
5 min read

Named capture groups make regular expressions more readable and maintainable. Here's how to use them.

Basic Syntax#

1// Traditional numbered groups 2const dateRegex = /(\d{4})-(\d{2})-(\d{2})/; 3const match = dateRegex.exec('2024-01-15'); 4console.log(match[1]); // '2024' - what is this? 5console.log(match[2]); // '01' - month or day? 6console.log(match[3]); // '15' 7 8// Named groups - much clearer 9const namedDateRegex = /(?<year>\d{4})-(?<month>\d{2})-(?<day>\d{2})/; 10const namedMatch = namedDateRegex.exec('2024-01-15'); 11 12console.log(namedMatch.groups.year); // '2024' 13console.log(namedMatch.groups.month); // '01' 14console.log(namedMatch.groups.day); // '15'

String.match()#

1const pattern = /(?<protocol>https?):\/\/(?<domain>[^\/]+)(?<path>\/.*)?/; 2 3const url = 'https://example.com/path/to/page'; 4const match = url.match(pattern); 5 6if (match) { 7 const { protocol, domain, path } = match.groups; 8 console.log(`Protocol: ${protocol}`); // 'https' 9 console.log(`Domain: ${domain}`); // 'example.com' 10 console.log(`Path: ${path}`); // '/path/to/page' 11} 12 13// Non-matching optional groups return undefined 14const simpleUrl = 'https://example.com'; 15const simpleMatch = simpleUrl.match(pattern); 16console.log(simpleMatch.groups.path); // undefined

String.matchAll()#

1const text = 'Call 555-123-4567 or 555-987-6543 for info'; 2const phonePattern = /(?<area>\d{3})-(?<exchange>\d{3})-(?<number>\d{4})/g; 3 4// Get all matches with groups 5for (const match of text.matchAll(phonePattern)) { 6 const { area, exchange, number } = match.groups; 7 console.log(`(${area}) ${exchange}-${number}`); 8} 9// (555) 123-4567 10// (555) 987-6543 11 12// Convert to array 13const phones = [...text.matchAll(phonePattern)] 14 .map(m => m.groups); 15 16console.log(phones); 17// [ 18// { area: '555', exchange: '123', number: '4567' }, 19// { area: '555', exchange: '987', number: '6543' } 20// ]

String.replace()#

1// Reference named groups in replacement 2const dateStr = '2024-01-15'; 3const pattern = /(?<year>\d{4})-(?<month>\d{2})-(?<day>\d{2})/; 4 5// Using $<name> syntax 6const usFormat = dateStr.replace(pattern, '$<month>/$<day>/$<year>'); 7console.log(usFormat); // '01/15/2024' 8 9// Using replacement function 10const formatted = dateStr.replace( 11 pattern, 12 (match, year, month, day, offset, string, groups) => { 13 return `${groups.month}/${groups.day}/${groups.year}`; 14 } 15); 16 17// Simpler with destructuring 18const formatted2 = dateStr.replace(pattern, (...args) => { 19 const { year, month, day } = args.at(-1); // groups is last arg 20 return `${month}/${day}/${year}`; 21});

Backreferences#

1// Reference named group within same pattern using \k<name> 2const duplicateWords = /(?<word>\w+)\s+\k<word>/gi; 3 4const text = 'The the quick brown fox fox jumped'; 5console.log(text.match(duplicateWords)); 6// ['The the', 'fox fox'] 7 8// Replace duplicates 9const fixed = text.replace(duplicateWords, '$<word>'); 10console.log(fixed); // 'The quick brown fox jumped' 11 12// Combine with numbered backreferences 13const pattern = /(?<quote>['"]).*?\k<quote>/g; 14const str = `He said "hello" and 'goodbye'`; 15console.log(str.match(pattern)); 16// ['"hello"', "'goodbye'"]

Complex Patterns#

1// Email parsing 2const emailPattern = /(?<username>[a-zA-Z0-9._%+-]+)@(?<domain>[a-zA-Z0-9.-]+)\.(?<tld>[a-zA-Z]{2,})/; 3 4const email = 'user.name@example.com'; 5const { username, domain, tld } = email.match(emailPattern).groups; 6console.log({ username, domain, tld }); 7// { username: 'user.name', domain: 'example', tld: 'com' } 8 9// Time parsing 10const timePattern = /(?<hours>\d{1,2}):(?<minutes>\d{2})(?::(?<seconds>\d{2}))?(?<period>\s*[AP]M)?/i; 11 12const time1 = '14:30:45'; 13const time2 = '2:30 PM'; 14 15console.log(time1.match(timePattern).groups); 16// { hours: '14', minutes: '30', seconds: '45', period: undefined } 17 18console.log(time2.match(timePattern).groups); 19// { hours: '2', minutes: '30', seconds: undefined, period: ' PM' }

Parsing Structured Data#

1// Log line parser 2const logPattern = /\[(?<timestamp>[^\]]+)\]\s+(?<level>\w+)\s+(?<message>.+)/; 3 4const logLine = '[2024-01-15 10:30:45] ERROR Database connection failed'; 5const { timestamp, level, message } = logLine.match(logPattern).groups; 6 7console.log({ timestamp, level, message }); 8// { 9// timestamp: '2024-01-15 10:30:45', 10// level: 'ERROR', 11// message: 'Database connection failed' 12// } 13 14// CSV-like parsing 15const csvPattern = /(?<name>[^,]+),(?<age>\d+),(?<city>[^,]+)/; 16const row = 'John Doe,30,New York'; 17const data = row.match(csvPattern).groups; 18 19console.log(data); 20// { name: 'John Doe', age: '30', city: 'New York' }

Optional Groups#

1// Handle optional parts gracefully 2const versionPattern = /v?(?<major>\d+)(?:\.(?<minor>\d+))?(?:\.(?<patch>\d+))?(?:-(?<prerelease>[a-z]+))?/i; 3 4function parseVersion(str) { 5 const match = str.match(versionPattern); 6 if (!match) return null; 7 8 return { 9 major: parseInt(match.groups.major), 10 minor: match.groups.minor ? parseInt(match.groups.minor) : 0, 11 patch: match.groups.patch ? parseInt(match.groups.patch) : 0, 12 prerelease: match.groups.prerelease || null, 13 }; 14} 15 16console.log(parseVersion('v2.1.3')); // { major: 2, minor: 1, patch: 3, prerelease: null } 17console.log(parseVersion('1.0')); // { major: 1, minor: 0, patch: 0, prerelease: null } 18console.log(parseVersion('3.0.0-beta')); // { major: 3, minor: 0, patch: 0, prerelease: 'beta' }

Validation Functions#

1// Password validation with named captures 2const passwordPattern = /^(?=.*(?<lowercase>[a-z]))(?=.*(?<uppercase>[A-Z]))(?=.*(?<digit>\d))(?=.*(?<special>[!@#$%^&*])).{8,}$/; 3 4function validatePassword(password) { 5 const match = password.match(passwordPattern); 6 7 if (!match) { 8 return { 9 valid: false, 10 missing: getMissingRequirements(password), 11 }; 12 } 13 14 return { valid: true }; 15} 16 17function getMissingRequirements(password) { 18 const missing = []; 19 if (!/[a-z]/.test(password)) missing.push('lowercase'); 20 if (!/[A-Z]/.test(password)) missing.push('uppercase'); 21 if (!/\d/.test(password)) missing.push('digit'); 22 if (!/[!@#$%^&*]/.test(password)) missing.push('special'); 23 if (password.length < 8) missing.push('length'); 24 return missing; 25}

Template Parsing#

1// Parse template variables 2const templatePattern = /\{\{(?<variable>\w+)(?:\|(?<filter>\w+))?\}\}/g; 3 4const template = 'Hello {{name|uppercase}}, your balance is {{balance}}'; 5 6function parseTemplate(template, data, filters = {}) { 7 return template.replace(templatePattern, (match, ...args) => { 8 const { variable, filter } = args.at(-1); 9 let value = data[variable] ?? match; 10 11 if (filter && filters[filter]) { 12 value = filters[filter](value); 13 } 14 15 return value; 16 }); 17} 18 19const result = parseTemplate( 20 template, 21 { name: 'John', balance: '$100' }, 22 { uppercase: (s) => s.toUpperCase() } 23); 24 25console.log(result); // 'Hello JOHN, your balance is $100'

RegExp.exec() Iteration#

1const pattern = /(?<tag>@\w+)/g; 2const text = 'Contact @support or @sales for help'; 3 4// Using exec() in a loop 5let match; 6while ((match = pattern.exec(text)) !== null) { 7 console.log(`Found ${match.groups.tag} at index ${match.index}`); 8} 9// Found @support at index 8 10// Found @sales at index 20 11 12// Reset lastIndex for reuse 13pattern.lastIndex = 0;

Best Practices#

Naming: ✓ Use descriptive names ✓ Follow camelCase ✓ Keep names short but clear ✓ Match domain terminology Patterns: ✓ Group logically related parts ✓ Use non-capturing (?:) for structure ✓ Document complex patterns ✓ Test edge cases Benefits: ✓ Self-documenting code ✓ Easier maintenance ✓ Safer refactoring ✓ Better error messages Avoid: ✗ Overly long group names ✗ Mixing numbered and named ✗ Complex nested groups ✗ Forgetting undefined checks

Conclusion#

Named capture groups make regular expressions more readable and maintainable. Use the (?<name>...) syntax to create groups, access them via match.groups, and reference them in replacements with $<name> or backreferences with \k<name>. Always check for undefined when using optional groups.

Share this article

Help spread the word about Bootspring