Regex Replace: Find & Replace with Regular Expressions
Regex find-and-replace is one of the most powerful text manipulation tools available to developers. Whether you are reformatting thousands of dates, scrubbing HTML from user content, converting naming conventions across a codebase, or batch-processing files in the terminal, understanding capture groups and back-references unlocks a whole category of transformations that are impossible with plain string replace.
The Problem with Plain String Replace
Standard string replacement is exact: you give it the string to find and the string to substitute. It works perfectly when the match is always identical. But real-world text is messy - dates come in five formats, phone numbers have varying separators, HTML attributes can appear in any order, and log entries have timestamps that are never quite the same.
Regex replacement solves this by letting you:
- Match patterns rather than exact strings
- Capture parts of the match with groups and reuse them in the replacement
- Apply transformations globally across an entire document with a single call
- Combine conditions with lookaheads and lookbehinds for context-aware substitution
How Regex Replace Works: Capture Groups and Back-References
The core mechanic of regex replace is the capture group. Wrap any part of your pattern in parentheses to capture it. In the replacement string, reference it with $1, $2, etc. (JavaScript, sed) or \1, \2 (Python, grep, Vim).
// Pattern: (\w+)\s+(\w+)
// Input: "John Smith"
// Replace: $2, $1
// Output: "Smith, John"
Group numbering starts at 1 from left to right based on opening parentheses. Named groups ((?<name>...)) are supported in most modern engines and make complex replacements readable.
Real Examples: JavaScript
Reformat Dates: MM/DD/YYYY to YYYY-MM-DD
'03/26/2026'.replace(/(\d{2})\/(\d{2})\/(\d{4})/, '$3-$1-$2');
// Output: '2026-03-26'
// Global replace on a multi-line string
const log = `Entry 03/15/2026 - User login
Entry 03/16/2026 - File upload`;
log.replace(/(\d{2})\/(\d{2})\/(\d{4})/g, '$3-$1-$2');
// Entry 2026-03-15 - User login
// Entry 2026-03-16 - File upload
Strip HTML Tags
const html = '<p>Hello <strong>world</strong>!</p>';
html.replace(/<[^>]*>/g, '');
// Output: 'Hello world!'
Note: this is fine for simple cases but use a proper HTML parser (DOMParser in browser, cheerio in Node.js) when parsing user-supplied HTML to avoid XSS vectors.
camelCase to kebab-case
'backgroundColor'.replace(/([a-z])([A-Z])/g, '$1-$2').toLowerCase();
// Output: 'background-color'
'myAPIEndpoint'.replace(/([a-z])([A-Z])/g, '$1-$2').toLowerCase();
// Output: 'my-api-endpoint' (note: consecutive capitals handled)
snake_case to camelCase
'user_first_name'.replace(/_([a-z])/g, (_, c) => c.toUpperCase());
// Output: 'userFirstName'
This uses a callback function as the replacement - a powerful JavaScript feature that lets you apply any transformation to each captured group.
Redact Sensitive Data
// Mask credit card numbers, keep last 4 digits
'Card: 4111-1111-1111-1234'.replace(/\b\d{4}[- ]\d{4}[- ]\d{4}[- ](\d{4})\b/, 'Card: ****-****-****-$1');
// Output: 'Card: ****-****-****-1234'
// Mask email addresses
'Contact: alice@example.com'.replace(/(\w{2})\w+(@\w+\.\w+)/, '$1***$2');
// Output: 'Contact: al***@example.com'
Normalize Whitespace
// Collapse multiple spaces to single space and trim
' too many spaces '.replace(/\s+/g, ' ').trim();
// Output: 'too many spaces'
// Replace all newline variants with Unix newline
text.replace(/\r\n|\r/g, '\n');
Test Your Regex Replace Patterns Live
Write your find pattern, enter your replacement string, and see the output instantly. Supports flags (g, i, m), capture group highlighting, and step-through matching. Free, runs entirely in your browser.
Open Regex Tester →Real Examples: Python (re.sub)
Python's re.sub(pattern, replacement, string) works similarly to JavaScript. Back-references in replacement strings use \1 syntax:
import re
# Reformat ISO dates to US format
text = 'Meeting on 2026-03-26 and follow-up on 2026-04-01'
result = re.sub(r'(\d{4})-(\d{2})-(\d{2})', r'\2/\3/\1', text)
# Output: 'Meeting on 03/26/2026 and follow-up on 04/01/2026'
# Remove Python-style comments from code
code = 'x = 1 # increment x\ny = 2 # set y'
re.sub(r'\s*#[^\n]*', '', code)
# Output: 'x = 1\ny = 2'
# Normalize phone numbers to E.164 format
phone = '(555) 867-5309'
re.sub(r'\D', '', phone) # Strip non-digits first
# Then format: re.sub(r'(\d{3})(\d{3})(\d{4})', r'+1\1\2\3', digits)
# Using a function as replacement (titlecase words after colon)
def titlecase_after_colon(m):
return ': ' + m.group(1).title()
re.sub(r':\s*(\w+)', titlecase_after_colon, 'status: active')
Real Examples: sed (Command Line)
sed is the Unix stream editor. Its substitution command is s/pattern/replacement/flags. Back-references use \1 syntax. The g flag replaces all occurrences on a line; without it, only the first match is replaced.
# Basic substitution
echo 'Hello world' | sed 's/world/universe/'
# Output: Hello universe
# Global replacement (all occurrences per line)
echo 'aaa bbb aaa' | sed 's/aaa/xxx/g'
# Output: xxx bbb xxx
# Capture group: swap first and last name
echo 'Smith John' | sed 's/\(\w\+\) \(\w\+\)/\2 \1/'
# Output: John Smith
# In-place file edit (GNU sed)
sed -i 's/http:\/\//https:\/\//g' config.txt
# Delete blank lines
sed '/^[[:space:]]*$/d' file.txt
# Add prefix to each line
sed 's/^/PREFIX: /' file.txt
# Replace only on lines matching a condition
sed '/ERROR/s/\[WARN\]/[ERROR]/' app.log
On macOS, BSD sed requires a backup extension with
-i: usesed -i '' 's/old/new/g' file.txt. GNU sed (Linux) acceptssed -i 's/old/new/g' file.txtwithout the empty string.
Step-by-Step: Regex Replace Workflow
- Identify the pattern: What does the text you want to change look like? Write a pattern that matches it and nothing else. Start specific.
- Decide what to capture: Which parts of the match do you want to reuse in the replacement? Wrap those in parentheses.
- Write the replacement: Use
$1,$2(or\1,\2) to reference captured groups. Add any literal text you need around them. - Choose your flags: Do you need global (
g), case-insensitive (i), or multiline (m) matching? - Test on a sample: Always test on a representative sample before applying to production data or files.
- Apply and verify: Apply the replacement and spot-check the output. For file operations, keep a backup.
Advanced: Lookaheads in Replacements
Lookaheads let you match based on context without including the context in the match itself. This is useful when you want to replace something only when it is followed or preceded by specific text:
// Add a comma before a closing brace ONLY if not already there
// Input: {a: 1\n b: 2\n}
// Replace trailing whitespace+newline before } with comma+newline
text.replace(/([^\s,])\s*\n(\s*\})/g, '$1,\n$2');
// Insert "px" after bare numbers in CSS values
'margin: 10 20 5 15;'.replace(/(\b\d+)\b(?!\s*px)/g, '$1px');
// Output: 'margin: 10px 20px 5px 15px;'
Common Mistakes
- Forgetting the global flag:
'aaa'.replace(/a/, 'b')returns'baa', not'bbb'. Add thegflag. - Back-reference syntax mismatch: JavaScript replacement strings use
$1; sed and Python use\1. Mixing them up is a common error when copying patterns between environments. - Escaping issues: In a JavaScript string replacement
'$1'is a back-reference. If you literally want to insert$1in the output, escape it as'$$1'. - Greedy matching consuming too much:
<.*>matches from the first<to the last>on a line. Use<[^>]*>or the lazy<.*?>instead. - Not accounting for multiline input: In JavaScript,
^and$match the start/end of the whole string by default. Add themflag to match per-line.
Use our free tool here → Regex Tester
Frequently Asked Questions
How do I use a capture group in a regex replacement in JavaScript?
Use parentheses in your pattern to create capture groups, then reference them in the replacement string with $1, $2, etc. For example: '2026-03-26'.replace(/(\d{4})-(\d{2})-(\d{2})/, '$2/$3/$1') outputs '03/26/2026'. You can also pass a function as the second argument to replace - the function receives the full match and all captured groups as arguments.
What is the difference between replace and replaceAll in JavaScript?
String.prototype.replace() with a string (not regex) only replaces the first occurrence. replaceAll() replaces all occurrences. With a regex, replace() replaces all occurrences if you use the g flag. replaceAll() with a regex requires the g flag or it throws a TypeError.
How does Python re.sub differ from JavaScript replace?
The API differs slightly. Python: re.sub(pattern, replacement, string, count=0, flags=0). The count parameter limits replacements (0 means unlimited). Back-references in replacement strings use \1 not $1. You can pass a callable as the replacement, receiving a match object and returning the replacement string - giving you full access to all match data.
How do I do a case-insensitive find and replace with regex?
Add the i flag. In JavaScript: str.replace(/pattern/gi, 'replacement'). In Python: re.sub(r'pattern', 'replacement', text, flags=re.IGNORECASE). In sed: sed 's/pattern/replacement/gi' (GNU sed supports the I flag for case-insensitivity).
Can regex replace be used to reformat entire files?
Yes. sed -i 's/pattern/replacement/g' file.txt edits a file in place. In Python, read the file, apply re.sub, and write it back. For multiple files, combine with shell globbing: sed -i 's/v1/v2/g' src/**/*.js or use find with -exec. Always make a backup before bulk in-place edits.
What is the risk of regex replace in production code?
The main risks are unintended matches (the pattern is broader than intended), catastrophic backtracking on long input strings (certain patterns with nested quantifiers), and encoding issues when processing binary or non-UTF-8 files. Mitigate by anchoring patterns, testing edge cases, limiting input length for user-supplied text, and using a regex engine timeout if available.
Usman has 10+ years of experience securing enterprise infrastructure, managing high-traffic servers, and building zero-knowledge security tools. Read more about the author.