String.prototype.replaceAll()

Added in 2021 to close a twenty-year-old papercut. Its real advantage over a /g regex is that the search text needs no escaping — which matters whenever that text is a variable.

String methodES2021Live demo
Common call
s.replaceAll('-', '+')
Returns
a new string — assign it
Replaces
s.replace(/-/g, '+') and split().join() hacks
Watch out
a RegExp argument MUST have the g flag or it throws
string.replaceAll(patternpattern — A string matches literally, every time, with no regex interpretation. A RegExp is allowed but must carry the g flag.type: string | RegExp · required, replacementreplacement — Same rules as replace: $& $1 $$ are special in a string, while a function replacer inserts its return value verbatim.type: string | Function · required)
→ string

Demo

Live evaluation
Try:
Inputs
sstringthe source string
searchstringtext to find
replstringreplacement text
Output
'a-b-c'.replaceAll('-', '+')
'a+b+c'

Compare the first case with the same call on the replace page: here both hyphens change. The fourth case is the quiet advantage — 'a.b' is treated as three literal characters, so 'axb' is left alone. Build the same thing as a regex and the dot becomes a wildcard that matches it. The last case is the one genuine oddity: an empty search string matches at every boundary including both ends, so 'aaa' becomes '-a-a-a-'.

Parameters

NameTypeRequiredDescription
patternstring | RegExpyesA string matches literally, every time, with no regex interpretation. A RegExp is allowed but must carry the g flag.
replacementstring | FunctionyesSame rules as replace: $& $1 $$ are special in a string, while a function replacer inserts its return value verbatim.

Return value

string — A NEW string with every occurrence replaced. The original is unchanged, as always with strings.

Common patterns

Substitute user-supplied text
The main reason to prefer it over a regex.
const out = text.replaceAll(needle, replacement);
Normalise separators
Chained calls read clearly enough.
const slug = title.trim().toLowerCase().replaceAll(' ', '-');
Strip a character entirely
An empty replacement deletes.
const digits = phone.replaceAll('-', '');

Examples

1. Every occurrence
'a-b-c'.replaceAll('-', '+')
Returns
'a+b+c'
2. replace differs
'a-b-c'.replace('-', '+')
Returns
'a+b-c'
3. Dots are literal
'a.b axb'.replaceAll('a.b', '-')
Returns
'- axb'
4. Regex would not be
'a.b axb'.replace(/a.b/g, '-')
Returns
'- -'
5. Empty search
'aaa'.replaceAll('', '-')
Returns
'-a-a-a-'
6. Non-global throws
'aa'.replaceAll(/a/, 'b')
Returns
TypeError: String.prototype.replaceAll called with a non-global RegExp argument

Pitfalls

1. A RegExp without the g flag throws
Deliberate: the method is named replaceAll, so it refuses a pattern that could only replace one. This is the opposite of replace, which quietly accepts either — and it is the one way replaceAll can fail where replace would not.
Throws
'aa'.replaceAll(/a/, 'b')
TypeError: String.prototype.replaceAll called with a non-global RegExp argument
Add the flag
'aa'.replaceAll(/a/g, 'b')
'bb'
2. It still returns a new string
Same immutability rule as every other string method. Calling it as a bare statement changes nothing, which is easy to miss when converting a loop that used to mutate an array.
Discarded
let s = 'a-b';
s.replaceAll('-', '+');
s
'a-b'
Assign it
let s = 'a-b';
s = s.replaceAll('-', '+');
s
'a+b'
3. $ in the replacement is still special
Switching from replace to replaceAll does not make the replacement string literal. A replacement containing $& or $1 — often one built from user input — expands exactly as it would with replace.
Expands
'x'.replaceAll('x', '$&$&')
'xx'
Function replacer
'x'.replaceAll('x', () => '$&$&')
'$&$&'
4. An empty search string inserts everywhere
It matches at every position between characters plus both ends, so the replacement is sprinkled throughout. Guard against an empty needle when the search text comes from an input box, or a single stray keystroke mangles the whole string.
Sprinkled
'aaa'.replaceAll('', '-')
'-a-a-a-'
Guard it
const out = needle ? s.replaceAll(needle, r) : s;
unchanged
5. ES2021 — not in older runtimes
Node 15+ and 2020-era browsers. Older targets need the /g regex form, with the search text escaped if it is not a literal.
Missing
s.replaceAll('-', '+')
TypeError: s.replaceAll is not a function
Regex form
s.replace(/-/g, '+')
same result

When to use

Use it
  • Replacing every occurrence of literal text
  • The search text is a variable that may contain regex metacharacters
  • Stripping a character out of a string entirely
  • Anywhere a /g regex existed only to get global behaviour
Reach for something else
  • You genuinely want one replacement → replace
  • The pattern is a real pattern → replace with a RegExp
  • You need the pieces → split
  • Targeting runtimes older than 2021 → the /g regex form

Notes

Complexity
O(n) over the string for a literal pattern
Return
A new string; the original is untouched
CPython impl
V8: Builtins-string-replaceall
Memory
Allocates the result string
Thread-safe
Single-threaded; a literal pattern carries no lastIndex state, unlike a shared /g regex

FAQ

No — for literal text it is generally the same or faster, because the engine can use a plain substring search instead of running the regex machinery. Correctness is the better argument anyway: no escaping, no lastIndex state.

History

ES2021
replaceAll added, closing a long-standing gap — replace had been first-match-only for string patterns since ES3.