JavaScript String replaceAll() Method: A Practical, Production-Ready Guide

I still remember the first time a support ticket landed on my desk because a user’s name showed up twice in a generated invoice. The root cause wasn’t some exotic bug. It was a simple string replacement that only changed the first match. That tiny detail cost a few hours of debugging and an awkward email. If you write JavaScript, you’ll run into this exact class of problems: sanitizing user input, updating templates, reformatting logs, or rewriting URLs. When you need every occurrence updated, you want a tool that does exactly that and nothing more.

That’s where replaceAll() earns its keep. It’s straightforward, predictable, and—most importantly—built into the language. I’ll walk you through how it works, why it behaves the way it does, and how I use it in production code. You’ll see how it differs from replace(), when a regular expression is the right choice, and the small edge cases that can still bite you if you ignore them. By the end, you’ll have a clear, repeatable mental model and a handful of patterns you can drop into real projects.

Why replaceAll() exists (and when I reach for it)

When replace() was the only option, I had two paths: call replace() repeatedly in a loop, or use a regular expression with a global flag. Both work, but they’re easy to misuse. Replacing in a loop can skip matches if you re-scan with stale indexes, and a regular expression introduces complexity even for plain text. replaceAll() removes that friction by expressing the intent directly: replace every occurrence.

In practice, I reach for replaceAll() when the pattern is a plain substring and I want zero regex overhead or ambiguity. It reads like a sentence, it’s easy to scan in a code review, and it matches how I think about the operation. If I’m dealing with user content, I want the code to be obvious and safe for future me.

One simple analogy I use with junior engineers: replace() is like a pencil eraser that works on the first smudge you touch. replaceAll() is a paint roller—you cover the whole wall in one pass. You can still use a brush (regex) for fine details, but the roller is perfect for broad, uniform changes.

The mental model and syntax you can keep in your head

The method signature is short:

const newString = originalString.replaceAll(regexpOrSubstring, replacementOrFunction);

Two important rules define how it behaves:

1) It returns a new string. The original string never changes.

2) It replaces every match, not just the first.

That’s it. Everything else is a variation on the input and the replacement.

String immutability matters

Strings in JavaScript are immutable. That means every replacement yields a new string. I bring this up because it affects performance, memory, and the way you structure code. If you call replaceAll() in a loop, you are building a new string every iteration. For small strings, it’s fine. For large strings, you may want to group changes or use a different strategy.

Here’s a minimal example that keeps this behavior visible:

const original = "Error: disk full. Error: retry later.";

const updated = original.replaceAll("Error", "Warning");

console.log(original); // "Error: disk full. Error: retry later."

console.log(updated); // "Warning: disk full. Warning: retry later."

You can depend on this immutability. It makes string operations safe in multi-step transformations because you never mutate a shared value by accident.

Substring vs regular expression: pick the right tool

replaceAll() accepts either a substring or a regular expression as the first parameter. I keep a simple decision tree:

  • If I’m matching fixed text, I use a string literal.
  • If I need case-insensitive matching, word boundaries, or pattern matching, I use a regular expression with the global flag.

Plain substring replacement

This is the most common use case. It’s clear and fast to read:

const template = "Hello, {name}. Your order {orderId} is ready.";

const filled = template

.replaceAll("{name}", "Ari")

.replaceAll("{orderId}", "TX-2049");

console.log(filled);

// "Hello, Ari. Your order TX-2049 is ready."

I prefer this pattern for small, obvious template replacements. It avoids regex pitfalls and keeps the code beginner-friendly.

Regular expression replacement

When you pass a regex, it must be global. This is a built-in guardrail. If you use a regex without the g flag, replaceAll() throws a TypeError. That’s intentional: the method promises to replace all matches, so a non-global regex would violate the promise.

const note = "Coffee or coffee?";

const result = note.replaceAll(/coffee/ig, "tea");

console.log(result); // "tea or tea?"

If you forget g, you’ll get an error instead of a silent partial replacement. I consider that a good thing because it forces correctness early.

Replacement strings vs replacement functions

The second parameter can be a string or a function. I use a string when the replacement is constant, and a function when the replacement depends on the match.

Replacement string with special tokens

Just like replace(), the replacement string can include special tokens like $& for the matched substring, $1 for the first capture group, and so on. This is handy for simple rearrangements without a full callback.

const isbn = "ISBN 978-1-4028-9462-6";

const masked = isbn.replaceAll(/(\d{3})-(\d)-(\d{4})-(\d{4})-(\d)/g, "$1---*-$5");

console.log(masked); // "ISBN 978---*-6"

Replacement function for dynamic logic

A function gives you full control. You can compute based on the match, apply casing rules, or look up values in a map.

const roleMap = {

admin: "Administrator",

editor: "Content Editor",

viewer: "Read-Only Viewer",

};

const input = "Roles: admin, editor, viewer.";

const output = input.replaceAll(/admineditorviewer/g, (match) => roleMap[match]);

console.log(output);

// "Roles: Administrator, Content Editor, Read-Only Viewer."

This pattern shines in migrations and data normalization scripts. You can also use it to sanitize text without resorting to multi-step string slicing.

Traditional vs modern approaches (and why the new one wins)

I often see older codebases that still chain replace() with a regex or a loop. Here’s how I compare them today:

Approach

Traditional

Modern with replaceAll() —

— Replace fixed substring

str.replace(/foo/g, "bar")

str.replaceAll("foo", "bar") Readability

Regex required even for plain text

Reads like intent Error safety

Easy to forget g and replace once

Non-global regex throws, safer Template filling

Manual loops

Simple chained calls

I still use regex when the pattern is a true pattern. But for fixed strings, replaceAll() is the clearest and least error-prone choice in modern JavaScript.

Common mistakes I see (and how I avoid them)

I’ve reviewed a lot of bug reports tied to string replacement. These are the top mistakes I watch for:

1) Using a regex without the global flag

This is a fast path to a runtime error with replaceAll(). If you want a regex, always add g:

// Throws TypeError

"aaa".replaceAll(/a/, "b");

// Works

"aaa".replaceAll(/a/g, "b");

2) Assuming it mutates the original string

I still see code like this:

let s = "aaab";

s.replaceAll("a", "c");

console.log(s); // "aaab" (unchanged)

You must assign the result:

s = s.replaceAll("a", "c");

3) Passing a string that looks like a regex

A string parameter is not a regex, so this won’t match digits:

"ID-42".replaceAll("\d", "#"); // "ID-42" (unchanged)

If you need a pattern, use a regex:

"ID-42".replaceAll(/\d/g, "#"); // "ID-##"

4) Forgetting about Unicode and surrogate pairs

If you’re handling emoji or composed Unicode characters, replacement by substring can split or miss unexpected graphemes. In my experience, this matters most for user-generated content in global apps. If the input can contain combined characters, consider a library that can iterate by grapheme clusters.

5) Overusing replaceAll() inside tight loops

If you have a large text blob and you call replaceAll() repeatedly, you’re building many intermediate strings. I prefer doing multiple replacements in a single pass if the rules allow it, or switching to a streaming approach.

Patterns I use in real code

Here are a few scenarios where replaceAll() has saved me time in production work.

Sanitizing log lines

When logs contain sensitive data, I strip or mask identifiers before storage:

const line = "User 4921 accessed /billing/card/4111-1111-1111-1111";

const redacted = line

.replaceAll(/\b\d{4}-\d{4}-\d{4}-\d{4}\b/g, "---")

.replaceAll(/\b\d{4}\b/g, "####");

console.log(redacted);

// "User #### accessed /billing/card/---"

I intentionally run the card masking first and then the generic ID masking. That order matters; otherwise I might corrupt the placeholder I just inserted.

Normalizing whitespace in user input

Users paste text from spreadsheets, PDFs, and chat apps. replaceAll() makes cleanup predictable:

const raw = "  12\tMain\nStreet  ";

const normalized = raw

.replaceAll("\t", " ")

.replaceAll("\n", " ")

.replaceAll(/\s+/g, " ")

.trim();

console.log(normalized); // "12 Main Street"

Here I mix substring and regex replacements for clarity: literal tabs and newlines are explicit, and then a regex sweeps up remaining multiple spaces.

Template placeholders with safe fallback

When I need to fill templated content but also keep missing fields visible, I do this:

const data = { name: "Rina", city: "Oslo" };

const template = "Hello {name}, welcome to {city}. Your role is {role}.";

const filled = template.replaceAll(/\{(\w+)\}/g, (match, key) => {

// If a key is missing, return the placeholder so it‘s obvious

return Object.prototype.hasOwnProperty.call(data, key) ? data[key] : match;

});

console.log(filled);

// "Hello Rina, welcome to Oslo. Your role is {role}."

This keeps content honest. I’d rather see {role} in a preview email than send incorrect data.

URL cleanup and migration

When you migrate a site, you often need to rewrite URLs in stored content:

const html = "Docs";

const migrated = html.replaceAll("https://old.example.com", "https://docs.example.com");

console.log(migrated);

// "Docs"

It’s short, clear, and safe when the pattern is a literal string.

Performance notes without the myths

String replacement performance varies by engine and input size, so I don’t like quoting hard numbers. What I do see in practice: for small to moderate strings (say, a few kilobytes), replaceAll() is typically fast enough that you won’t measure a difference. For very large strings, repeated replacements can add noticeable time, often in the single-digit to tens-of-milliseconds range depending on the environment.

If you’re processing large files or logs, you can reduce the number of passes. For example, combine related rules into a single regex or do chunked streaming so you don’t build enormous intermediate strings. I also profile before spending time on micro-tuning; in real applications, network and I/O are often the actual bottlenecks.

One more practical note: if you find yourself doing a dozen replaceAll() calls in a row, it might be time to step back and reconsider the data flow. Perhaps a template engine, a structured parse, or a formatter would be a better long-term solution.

When I would not use replaceAll()

Even though I like the method, it’s not always the right tool. Here are a few cases where I pick something else:

  • Complex parsing: If I need context, like “replace only inside quotes,” I parse or tokenize first. Regex alone can get brittle.
  • HTML and XML: I avoid blind string replacements for markup. I use a DOM parser so I don’t corrupt structure.
  • Internationalized text: If I need to treat grapheme clusters as single units, I reach for a library designed for Unicode text.
  • Binary data: If I’m working with encoded content or buffers, I’ll work at the byte level, not the string level.

In each case, the issue isn’t that replaceAll() is wrong; it’s that the problem has a structural shape that strings can’t represent safely by themselves.

Debugging tips I use in code reviews

When I review a pull request involving replaceAll(), I check a few things quickly:

  • Does the replacement happen in the correct order?
  • Are we handling missing data correctly, or should we keep placeholders?
  • Is the regex global and tested against mixed-case input?
  • Is there a test covering a repeated match?
  • Are we mutating strings in a loop without realizing the cost?

If any of those are unclear, I ask for a small test case. Even a three-line test can reveal hidden issues. In 2026, I also lean on AI-assisted code review tools to scan for regex mistakes and missing g flags, but I still prefer a human check for intent.

A quick checklist you can keep nearby

I keep this mental checklist when using replaceAll():

  • Intent: Am I replacing every occurrence or just the first?
  • Type: Is my pattern a literal string or a real pattern?
  • Regex: If regex, does it include g?
  • Mutation: Did I assign the returned string?
  • Safety: Will this break markup or structured data?

If you can answer those in seconds, you’ll avoid most pitfalls.

Real-world edge cases that deserve a paragraph each

The method is small, but the inputs you feed it aren’t. Here are edge cases I’ve seen in production—and how I avoid the fallout.

Overlapping matches don’t double-count

replaceAll() doesn’t re-scan within a replacement. It finds non-overlapping matches in the original string, then replaces them. That matters when your pattern could overlap with itself.

const s = "aaaa";

const out = s.replaceAll("aa", "b");

console.log(out); // "bb" not "bab" or "bbb"

If you need overlapping behavior, you must use a different approach (for example, a lookahead regex or a manual scan with indexes).

Replacement strings can be interpreted

The replacement string has special tokens like $& or $1. If you want to insert a literal $, you have to escape it by doubling: $$.

const s = "Total: $5, $10, $15";

const out = s.replaceAll("$", "$$$");

console.log(out); // "Total: $$$5, $$$10, $$$15"

If you forget this, you’ll get surprising output when your replacement contains $.

Empty string patterns behave oddly

Replacing the empty string inserts the replacement between every character and at the boundaries. This is standard JavaScript behavior, but it’s a footgun if you do it by accident.

"abc".replaceAll("", "-");

// "-a-b-c-"

I almost never do this on purpose. If you see an empty pattern in a review, pause and confirm the intent.

Case sensitivity is literal unless you use regex

Strings are case-sensitive. If you want case-insensitive replacement, you need a regex with i and g.

const s = "Error ERROR error";

const out = s.replaceAll(/error/ig, "warning");

console.log(out); // "warning warning warning"

This is one of those pitfalls that’s easy to miss when the data looks consistent—until it isn’t.

A deeper look at replacement functions

The replacement function signature is often underused. It can take more parameters than most people realize. For a regex, it receives the match, capture groups, the offset, and the full string. That makes it extremely powerful.

const log = "[info] user=jade action=login; [warn] user=omar action=retry";

const scrubbed = log.replaceAll(/user=(\w+)/g, (match, user, offset, full) => {

const before = full.slice(0, offset);

const userIndex = (before.match(/user=/g) || []).length + 1;

return user=user${userIndex};

});

console.log(scrubbed);

// "[info] user=user1 action=login; [warn] user=user2 action=retry"

Is this the only way to solve it? No. But it shows that the replacement function can be used for position-aware transformations without additional parsing.

Practical scenario: user input sanitization that stays readable

I often need to sanitize data for display without making it unrecognizable. Here’s a small utility I actually use in projects. It replaces problematic whitespace, strips control characters, and normalizes repeated punctuation.

function sanitizeDisplayName(input) {

return input

.replaceAll(/\p{C}+/gu, "") // remove control characters (unicode category C)

.replaceAll(/\s+/g, " ")

.replaceAll("..", ".")

.trim();

}

console.log(sanitizeDisplayName(" Eva\n\n\u0007.. "));

// "Eva."

Notice that I use a Unicode property escape to remove control characters. That requires the u flag and is a regex-only feature—exactly the kind of situation where regex is the right choice. The rest of the transformations are simple and readable.

Practical scenario: transforming CSV-like data safely

When people ask for a quick hack to transform comma-separated data, I always warn them about quoting. But if the input is well-defined and you control it, replaceAll() can help you normalize fields before you parse.

const row = "  Alice , 25 , Seattle  ";

const cleaned = row

.replaceAll(" ,", ",")

.replaceAll(", ", ",")

.trim();

const [name, age, city] = cleaned.split(",");

console.log(name, age, city); // "Alice" "25" "Seattle"

This is not a full CSV parser. It’s a convenience step for a controlled environment where the input is predictable. That’s an important distinction to keep clear.

Practical scenario: log anonymization with maps and counters

If you’re anonymizing logs, you often need stable replacements: the same user should be mapped to the same pseudonym every time, across the entire string.

function anonymizeUsers(text) {

const map = new Map();

let counter = 1;

return text.replaceAll(/user=(\w+)/g, (match, user) => {

if (!map.has(user)) map.set(user, user${counter++});

return user=${map.get(user)};

});

}

const log = "user=alice action=login; user=bob action=logout; user=alice action=retry";

console.log(anonymizeUsers(log));

// "user=user1 action=login; user=user2 action=logout; user=user1 action=retry"

This is a great example of why a replacement function can be more expressive than an imperative loop. It’s readable, it’s deterministic, and it keeps the transformation in one place.

A careful comparison: replaceAll() vs split/join

Before replaceAll(), many developers used the classic split() + join() pattern for literal replacements. That still works, but it has tradeoffs.

const s = "foo foo foo";

const a = s.replaceAll("foo", "bar");

const b = s.split("foo").join("bar");

Both produce the same output for simple literals. But replaceAll() is clearer about intent, handles the replacement string tokens correctly, and supports regex patterns directly. I still use split/join occasionally when I want a very explicit literal replacement and I know the pattern will never be a regex. But in modern code, replaceAll() is usually the better default.

Building your own safe replacement helper

In team codebases, consistency matters. I often wrap replaceAll() in a tiny helper to make intent extra clear and to standardize error handling.

function replaceAllLiteral(haystack, needle, replacement) {

if (needle === "") return haystack; // avoid empty-pattern behavior

return haystack.replaceAll(needle, replacement);

}

function replaceAllRegex(haystack, regex, replacement) {

if (!regex.global) {

throw new TypeError("replaceAllRegex requires a global regex");

}

return haystack.replaceAll(regex, replacement);

}

This is not a requirement, but it helps onboarding and code reviews. It also prevents accidental empty-string replacements, which are surprisingly common when inputs are user-supplied.

Handling user-supplied patterns safely

If you allow users to provide a pattern, the safest path is to treat it as a literal string and escape it before making a regex. Here’s a standard escape helper:

function escapeRegexLiteral(value) {

return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");

}

function replaceUserString(haystack, userInput, replacement) {

const safe = escapeRegexLiteral(userInput);

const regex = new RegExp(safe, "g");

return haystack.replaceAll(regex, replacement);

}

Notice that I still use replaceAll() even though I’m building a regex. That’s because I want the global behavior and the explicit guarantee that every match is replaced. The guardrail matters.

A brief note on compatibility and environments

replaceAll() is widely supported in modern JavaScript engines, but if you target very old browsers or embedded runtimes, you may need a fallback. In those cases, I keep a small polyfill or fallback method using regex.

function safeReplaceAll(haystack, needle, replacement) {

if (typeof haystack.replaceAll === "function") {

return haystack.replaceAll(needle, replacement);

}

// Fallback for literal string needles only

if (typeof needle === "string") {

return haystack.split(needle).join(replacement);

}

// Fallback for regex

if (needle instanceof RegExp) {

if (!needle.global) throw new TypeError("Global regex required");

return haystack.replace(needle, replacement);

}

return haystack;

}

I prefer feature detection to environment detection. It’s simpler and more robust.

Using replaceAll() in templating without a full engine

Sometimes you don’t want a full templating library. You just need a simple, safe placeholder replacement. Here’s a slightly more complete example than the earlier snippet:

function fillTemplate(template, data) {

return template.replaceAll(/\{(\w+)\}/g, (match, key) => {

if (key in data) {

const value = data[key];

return value == null ? "" : String(value);

}

return match; // leave unknown placeholders unchanged

});

}

const template = "Hello {name}, your balance is {balance} {currency}.";

const data = { name: "Kim", balance: 42, currency: "USD" };

console.log(fillTemplate(template, data));

// "Hello Kim, your balance is 42 USD."

This is safe, readable, and easy to test. I’ve used this pattern in internal tools and small dashboards where a full templating engine would be overkill.

Edge case: replacing with a function and capturing groups

When you use capture groups in your regex, the replacement function gets them as arguments. This can keep your logic compact.

const text = "2025-12-01, 2026-01-11";

const reformatted = text.replaceAll(/(\d{4})-(\d{2})-(\d{2})/g, (match, y, m, d) => {

return ${m}/${d}/${y};

});

console.log(reformatted);

// "12/01/2025, 01/11/2026"

This is a clean alternative to parsing and reassembling with split().

When order of replacements changes the output

Order matters more than you might think. If one replacement introduces text that matches a later pattern, the sequence you choose will shape the result.

const text = "Errors: error and ERROR";

const out = text

.replaceAll(/error/ig, "issue")

.replaceAll("issue", "problem");

console.log(out); // "Errors: problem and problem"

If you reversed the order, you’d end up with a different result. In production code, I usually comment or group replacements by intent so the order is obvious.

A more complete pattern: multi-rule replacement in one pass

When performance matters and the replacements are simple, I sometimes use a single regex with a map. This avoids multiple passes.

const map = {

"http://": "https://",

"foo": "bar",

"cat": "dog",

};

const regex = new RegExp(Object.keys(map).map(escapeRegexLiteral).join("|"), "g");

function multiReplace(text) {

return text.replaceAll(regex, (match) => map[match]);

}

console.log(multiReplace("http://foo.com has a cat"));

// "https://bar.com has a dog"

This is one of those patterns you should use sparingly, but it can be a nice optimization when you have many literal replacements to perform in a single string.

Common pitfalls in team codebases

Here are the pitfalls I see most often when teams are moving to replaceAll():

  • Inconsistent replacement style: Some modules use regex everywhere, others use replaceAll() with literal strings. I try to standardize: literal string with replaceAll(), regex only when needed.
  • Unclear placeholders: If you use {name} and {Name} inconsistently, replacements may silently fail. I enforce a naming convention for placeholders.
  • Missing tests for repeated matches: I want at least one test that proves multiple replacements occur.
  • Surprising $ behavior: If the replacement contains $, tests should cover it explicitly.
  • Whitespace handling: Using replaceAll(" ", "") often removes meaningful spaces. I prefer trimming and normalization instead of blanking spaces blindly.

I’m not strict about any of these, but I do flag them in reviews because they often cause subtle bugs later.

Testing strategies that pay off quickly

If you’re writing tests for a replaceAll() transformation, focus on variety rather than volume. A small set of targeted cases beats a huge list of redundant ones.

Here’s a test checklist I use:

  • Input with zero matches
  • Input with one match
  • Input with multiple matches
  • Input with mixed case (if applicable)
  • Input with special characters like $, \n, or emoji

These five tests catch most issues quickly. If the replacement is order-sensitive, add one test that proves the final order is correct.

Performance considerations: ranges, not numbers

I avoid precise performance claims because they rarely hold across different machines and engines. But I can share a useful rule of thumb:

  • For strings under a few kilobytes, the difference between replace() with regex and replaceAll() is usually negligible.
  • For strings in the hundreds of kilobytes or larger, multiple passes start to show up. The difference might range from a tiny fraction of a millisecond to a handful of milliseconds per pass, depending on the environment.
  • If you’re doing thousands of replacements across large strings, a single-pass approach or streaming can be noticeably faster.

The key is to measure where it matters. I’ve been surprised more than once by what ends up being the actual bottleneck.

Alternatives and tradeoffs

Sometimes replaceAll() isn’t the best tool, even for simple tasks. Here’s how I think about alternatives:

  • replace() with regex: Great for one-off patterns, especially when you need to preserve older runtime compatibility.
  • split() + join(): Useful for extremely simple literal replacements and when you want to avoid regex entirely. Limited flexibility.
  • Template engines: Best for complex templating scenarios and HTML generation. Overkill for simple replacement tasks.
  • Parsing libraries: The safest option for structured content (HTML, XML, JSON-like strings).

If I can solve it with a literal replaceAll() in a readable way, I start there. If the logic becomes non-obvious, I step up the toolset.

AI-assisted workflows (and how I keep them safe)

I do use AI tools to draft regex patterns or identify repeated replacement mistakes. The trick is to treat the result as a starting point, not a final answer. I still validate:

  • Does the regex have a g flag?
  • Are there false positives or over-matches?
  • Does the replacement function handle null or undefined values?

AI can speed up the boring parts, but it can also hide complexity. replaceAll() keeps the intent explicit, and I want that clarity to survive even when tools are involved.

A small glossary for clarity

I keep these definitions in mind, especially when mentoring:

  • Match: The substring that fits your pattern.
  • Global: A regex flag (g) that finds all matches, not just the first.
  • Replacement string: The string inserted in place of the match. It can include tokens like $&.
  • Replacement function: A callback that computes the replacement per match.
  • Immutability: The original string never changes; you get a new string.

This is basic stuff, but it prevents miscommunication when you’re pairing or reviewing code.

A quick “decision map” you can reuse

When I’m in the middle of a feature and need to decide quickly, I ask myself:

1) Is it a literal substring? If yes, use replaceAll() with a string.

2) Do I need a pattern (case-insensitive, digit matching, boundaries)? If yes, use a global regex.

3) Do I need per-match logic? If yes, use a replacement function.

4) Is the content structured (HTML, JSON-like, code)? If yes, parse instead of replace.

This gets me to the right tool almost every time.

A more ambitious example: rewriting markdown links

Let’s say you have markdown content with links that need to be migrated to a new domain, but only for links that match a particular path. You can do it with a regex and a replacement function.

const markdown = "Visit Docs or Blog.";

const updated = markdown.replaceAll(/\((https?:\/\/old\.example\.com\/[^)]+)\)/g, (match, url) => {

if (url.startsWith("https://old.example.com/docs")) {

return (${url.replace("https://old.example.com", "https://docs.example.com")});

}

return match;

});

console.log(updated);

// "Visit Docs or Blog."

This is safe, targeted, and keeps the original formatting intact. It’s also a good example of where replaceAll() and a callback shine together.

Another practical scenario: building a mini slugifier

Slug generation is a common task for URLs. You can build a very simple slugifier with replaceAll() and a couple of regexes.

function slugify(text) {

return text

.toLowerCase()

.replaceAll(/[^a-z0-9\s-]/g, "")

.replaceAll(/\s+/g, "-")

.replaceAll(/-+/g, "-")

.replaceAll(/^-|-$/g, "");

}

console.log(slugify("Hello, World! JavaScript String replaceAll()"));

// "hello-world-javascript-string-replaceall"

This isn’t a full i18n slugifier, but for basic ASCII content it’s clean and reliable. It also demonstrates how replaceAll() fits into a pipeline of transformations.

One more subtlety: replaceAll() and empty matches in regex

Some regex patterns can match empty strings. If you use such a pattern with replaceAll(), you can get surprising output or even infinite-loop-like behavior in your mental model (though JavaScript handles it carefully by advancing the index).

const s = "abc";

const out = s.replaceAll(/\b/g, "|");

console.log(out); // "abc"

This can be useful in certain formatting tasks, but be careful. If you’re not intentionally matching empty strings, double-check your regex.

The “small but sharp” replaceAll() summary

The method is small, but it solves a surprisingly large class of problems. If you only remember one thing, remember this: replaceAll() is a clean way to express “every occurrence” without turning a simple task into a regex puzzle. That clarity pays off every time someone reads your code six months later—including you.

I recommend building a few utility helpers around replaceAll() for your domain: redaction rules for logs, template fillers for emails, and sanitizer functions for user input. Keep them small, test them with at least one repeated match, and be explicit about order. If you’re already using TypeScript, add narrow types for the keys you allow in templates so you catch missing placeholders at build time.

When you do need regex, treat it as a precise tool. A global flag is mandatory, and a well-named constant for your pattern makes your code far easier to review. For anything beyond simple substitution, reach for parsers or formatters rather than forcing strings to do structural work.

If you adopt these habits, you’ll spend less time chasing tiny text bugs and more time building features. And the next time a user reports a duplicated name, you’ll know exactly where to look.

Scroll to Top