escape and unescape

Deprecated since 1999 and still present in every engine. Documented here so you recognise it in old code and understand why the data it produced is often subtly corrupt.

Global functions (Annex B)ES1, Annex B since ES3Live demo
Common call
encodeURIComponent(s)
Returns
Latin-1 percent codes, plus %uXXXX
Replaces
nothing — encodeURIComponent replaces IT
Watch out
%uXXXX is not valid URL encoding and nothing else decodes it
escape(string), unescape(string)
→ string

Demo

Live evaluation
Try:
Inputs
sstringtext to encode
Output
[escape('a b'), encodeURIComponent('a b')]
['a%20b', 'a%20b']

The pair is [escape, encodeURIComponent]. For plain ASCII they agree, which is why escape survived so long without obvious breakage. The second case is the silent corruption: escape emits %E9, the single Latin-1 byte for é, while encodeURIComponent emits %C3%A9, the correct UTF-8 pair — so anything decoding escape output as UTF-8 gets a replacement character. The third case is worse: 中 becomes %u4E2D, a form invented by Netscape that no URL parser, server or standard understands.

Parameters

NameTypeRequiredDescription
stringstringyesThe text to encode. Code points up to 255 become %XX using the LATIN-1 byte; anything higher becomes %uXXXX, a form no standard recognises.

Return value

string — escape percent-encodes using Latin-1 byte values, and emits the non-standard %uXXXX form for code points above 255. unescape reverses it.

Common patterns

Use encodeURIComponent
The correct replacement, in every case.
const encoded = encodeURIComponent(value);
Decoding legacy escape output
unescape is the only thing that reads %uXXXX.
const text = unescape(legacyValue);
Migrating stored data
Decode with the old function, re-encode with the new.
const fixed = encodeURIComponent(unescape(stored));

Examples

1. ASCII matches
escape('a b')
Returns
'a%20b'
2. Latin-1 byte
escape('é')
Returns
'%E9'
3. UTF-8 is correct
encodeURIComponent('é')
Returns
'%C3%A9'
4. Non-standard form
escape('中')
Returns
'%u4E2D'
5. Proper UTF-8
encodeURIComponent('中')
Returns
'%E4%B8%AD'
6. unescape reverses it
unescape('%E9')
Returns
'é'

Pitfalls

1. %uXXXX is not valid URL encoding
Netscape invented it and nothing else implements it. A URL carrying %u4E2D will not decode on any server, and decodeURIComponent throws on it. Data encoded this way is effectively readable only by JavaScript calling unescape.
Nothing decodes it
decodeURIComponent(escape('中'))
URIError: URI malformed
Matching pair
decodeURIComponent(encodeURIComponent('中'))
'中'
2. Latin-1 output corrupts silently
The worse failure, because nothing throws. An accented letter becomes a single Latin-1 byte such as %E9, which is not valid UTF-8 — so a server decoding it as UTF-8 produces a replacement character. Accented names stored through escape come back mangled and nobody notices until a user complains.
Wrong bytes
escape('é')
'%E9' — invalid as UTF-8
Right bytes
encodeURIComponent('é')
'%C3%A9'
3. It does not escape + / @
escape leaves several characters that are reserved in URLs, including + and /, so its output is not safe to drop into a query string anyway. It was never a URL encoder — it predates the URL specification it is used with.
Not escaped
escape('a+b/c')
'a+b/c'
Escaped
encodeURIComponent('a+b/c')
'a%2Bb%2Fc'
4. It is Annex B, not the core language
Normatively optional — required only of web browsers. A conforming non-browser runtime may omit it, so code using it is not portable even though every current engine happens to ship it.
May be absent
escape('a')
ReferenceError in a conforming non-browser host
Always present
encodeURIComponent('a')
core language

When to use

Use it
  • Never in new code
  • Decoding data that was originally encoded with escape — unescape is the only thing that reads %uXXXX
  • Recognising it while reading something old
Reach for something else
  • Encoding anything → encodeURIComponent
  • Decoding modern percent encoding → decodeURIComponent
  • Non-ASCII text → escape corrupts it, silently or otherwise
  • Portable code → Annex B is optional outside browsers

Notes

Complexity
O(n) in the length of the string
Return
A new string; the input is unchanged
CPython impl
V8: Builtins-global-escape — the Annex B section
Memory
Allocates the result string
Thread-safe
Single-threaded

FAQ

Because removing it would break old pages, and the web platform does not break old pages. Annex B is the section of the specification that documents exactly these features — deprecated, normatively optional, and permanently present.

History

ES1
escape and unescape shipped with the Latin-1 and %uXXXX behaviour.
ES3
encodeURI and encodeURIComponent added with proper UTF-8; escape deprecated in the same edition.
ES5
Formally moved to Annex B as normatively optional legacy features.