String.prototype.toWellFormed() returns a new string with every lone UTF-16 surrogate replaced by the Unicode replacement character U+FFFD (�) (same API as MDN String.prototype.toWellFormed()). Learn why ill-formed text breaks encodeURI(), how valid emoji stay intact, pairing with isWellFormed(), five examples, and try-it labs.
01
Kind
Instance method
02
Returns
New string
03
Parameters
None
04
Fixes
Lone surrogates
05
Mutates?
No
06
Baseline
Since Oct 2023
Fundamentals
Introduction
JavaScript strings are stored as UTF-16. Characters above U+FFFF (including many emoji) need two code units called a surrogate pair. When only one half arrives, you get a lone surrogate — an ill-formed string.
toWellFormed() repairs that by swapping each orphan for U+FFFD (�), the standard Unicode replacement character. Engines can do this efficiently against the internal string data.
💡
Beginner tip
Use isWellFormed() to detect problems and toWellFormed() to fix them before APIs like encodeURI().
str.toWellFormed() walks the UTF-16 code units and replaces any unpaired high or low surrogate with U+FFFD. Complete pairs (real emoji) stay as they are.
Returns a new string — always (even if already well-formed).
Takes no parameters.
Lone surrogates become � (U+FFFD).
Does not change Unicode normalization — use normalize() for NFC/NFD.
Foundation
📝 Syntax
General form of String.prototype.toWellFormed:
JavaScript
str.toWellFormed()
Parameters
None — this method does not accept arguments.
Return value
A new string that is a copy of this string, with all lone surrogates replaced with U+FFFD. If the string was already well-formed, you still get a new string with the same content.
Prefer codePointAt / for...of over index math when cutting Unicode text. When halves slip through, toWellFormed() cleans them up.
Applications
🎯 Common Use Cases
Safe URIs — encodeURI(str.toWellFormed()) before networking.
Sanitize imports — text from buffers, files, or cut-and-paste bugs.
Detect + repair pipelines — pair with isWellFormed().
Not for NFC/NFD — use normalize() for composed vs decomposed forms.
Not everyday English — normal literals rarely need this.
🧠 How toWellFormed() Works
1
Scan UTF-16 units
Walk each code unit in the string.
Scan
2
Keep valid pairs
High + low surrogates that form a real character stay intact.
Keep
3
Replace orphans
Lone high or low surrogates become U+FFFD (�).
Fix
4
✅
Return a new string
Original text is never mutated.
Important
📝 Notes
Always returns a new string, even when nothing needed fixing.
Same replacement character many APIs use automatically (for example TextEncoder).
Slicing mid-emoji with slice / charAt is a common source of lone surrogates.
Not the same as normalize() — that changes Unicode forms, not surrogates.
Baseline widely available since October 2023 — polyfill older runtimes if required.
Compatibility
Browser & Runtime Support
String.prototype.toWellFormed() is Baseline Widely available — supported across modern browsers since October 2023.
✓ Baseline · Widely available
String.toWellFormed()
Safe for current production browsers. Pair with isWellFormed() to detect, then repair before encodeURI or other strict APIs.
ModernWidely available
Google ChromeSupported · Desktop & Mobile
Full support
Mozilla FirefoxSupported · Desktop & Mobile
Full support
Apple SafariSupported · macOS & iOS
Full support
Microsoft EdgeSupported · Chromium
Full support
Internet ExplorerNo native support · Use a polyfill
Polyfill
OperaSupported · Modern versions
Full support
Samsung InternetSupported · Android
Full support
BunSupported · JavaScript runtime
Supported
DenoSupported · JavaScript runtime
Supported
Node.jsSupported · Server runtime
Supported
Android WebViewSupported · Modern WebView
Full support
toWellFormed()Excellent
Bottom line: Use String.toWellFormed() to replace lone UTF-16 surrogates with U+FFFD. Check with isWellFormed() when you only need a boolean.
Wrap Up
Conclusion
String.prototype.toWellFormed() is the built-in way to sanitize ill-formed UTF-16 — swap lone surrogates for � so APIs like encodeURI stay happy. Pair it with isWellFormed() for detect-then-fix flows.
Call toWellFormed() before encodeURI on untrusted text
Pair with isWellFormed() when you need a check first
Prefer code-point-aware APIs when slicing Unicode
Treat the result as a new string
Polyfill if you must support pre-2023 engines
❌ Don’t
Expect it to fix NFC/NFD — that is normalize()
Assume slice(0, 1) on emoji stays well-formed
Forget it always allocates a new string
Confuse � with “delete the character”
Skip sanitizing binary/network text that may be cut mid-pair
Summary
Key Takeaways
Knowledge Unlocked
Five things to remember about String.toWellFormed()
Lone surrogates become U+FFFD — original unchanged.
5
Core concepts
📝01
Returns
new string
API
🛠02
Fixes
lone surrogates
Role
�03
Replace
U+FFFD
Char
🔍04
Pair
isWellFormed()
Detect
⚡05
Mutates
no
Immutable
❓ Frequently Asked Questions
String.prototype.toWellFormed() returns a new string where every lone UTF-16 surrogate is replaced with the Unicode replacement character U+FFFD (�). Well-formed strings are copied unchanged in content.
UTF-16 uses pairs of code units for characters above U+FFFF. A lone (orphan) surrogate is one half without its matching partner — for example "\uD800" alone.
No. Call it with empty parentheses: str.toWellFormed().
isWellFormed() returns true or false. toWellFormed() returns a repaired string. Check with isWellFormed(); fix with toWellFormed().
encodeURI throws URIError on ill-formed strings. Passing str.toWellFormed() replaces lone surrogates first so encoding succeeds (replacement becomes %EF%BF%BD).
No. Strings are immutable. It always returns a new string — even when the input was already well-formed.
Did you know?
When ill-formed strings reach APIs like TextEncoder, they are often converted with the same U+FFFD replacement. toWellFormed() lets you do that step explicitly — and more efficiently than a hand-written loop.