Percent-encode text for URLs and decode it back.
URLs may only contain a restricted set of ASCII characters. Anything outside that set — spaces, accented letters, emoji, and characters that have structural meaning like ?, & and # — has to be percent-encoded: replaced by a % followed by the two hex digits of each UTF-8 byte.
Characters outside ASCII encode to multiple bytes. The character e-acute becomes %C3%A9 — two bytes in UTF-8 — and an emoji typically becomes four percent-escapes.
The query coffee & cake? as a parameter value encodes to:
Encoding the & is what prevents it from being read as a separator between parameters. Leaving it raw would split the query into q=coffee and a nonsense second parameter — the mechanism behind a whole class of parameter injection bugs.
RFC 3986 divides characters into two groups. Unreserved characters — A-Z a-z 0-9 - _ . ~ — never need encoding. Reserved characters have structural meaning and must be encoded when they appear as data rather than as delimiters.
| Character | Encoded | Structural role |
|---|---|---|
| Space | %20 | Terminates a URL in many parsers |
| ? | %3F | Starts the query string |
| & | %26 | Separates query parameters |
| = | %3D | Separates key from value |
| # | %23 | Starts the fragment |
| / | %2F | Separates path segments |
| + | %2B | Means space in form encoding |
| % | %25 | Starts an escape sequence |
There are two closely related encodings that differ in exactly one respect. In application/x-www-form-urlencoded, used by HTML form submissions, a space is encoded as +. In standard percent-encoding, a space is %20.
The consequence is that a literal plus sign in form data must be encoded as %2B, or it will be decoded as a space. This is why phone numbers in international format so often arrive with the leading + mangled into a space, and why email addresses using the user+tag@example.com convention break in badly written forms. When in doubt, encode the plus.
This is the most common mistake. JavaScript offers two functions:
encodeURIComponent() encodes reserved characters including / ? & = #. Use it for individual parameter values.encodeURI() leaves those characters alone. Use it only on a complete URL that is already correctly structured.Running encodeURI on a parameter value leaves any & inside it intact, so the value breaks out of its parameter. Running encodeURIComponent on a whole URL mangles the :// and every slash. Encode each component separately, then assemble the URL.
Encoding an already-encoded string escapes the percent signs themselves: %20 becomes %2520. Decoding once then yields %20 as literal text rather than a space. This produces URLs containing visible %2520 sequences and is almost always a sign that a value passed through two encoding layers — a common outcome when a proxy, framework and template each try to be helpful. The fix is to establish which layer is responsible and remove the duplicate, not to decode twice.
Domain names use a different mechanism. Non-ASCII domains are converted by Punycode into an ASCII form beginning xn--, so a domain in Greek or Devanagari script becomes something like xn--mgbh0fb.example. This is separate from percent-encoding, which applies to the path, query and fragment. Browsers display the Unicode form while resolving the Punycode one, and the gap between what is displayed and what resolves has been used for phishing — visually identical characters from different scripts can produce a lookalike domain.
Both can represent a space. %20 is standard percent-encoding and works anywhere in a URL. + means a space only in form-encoded data. A literal plus sign must always be written %2B, otherwise it will be read as a space.
encodeURIComponent for individual parameter values, because it escapes &, =, ? and /. encodeURI only for a complete, already-structured URL. Using the wrong one either breaks the URL structure or lets a value break out of its parameter.
Double encoding. A string that was already percent-encoded got encoded again, turning the % of %20 into %25. Find the layer applying the second encoding rather than decoding twice to compensate.
Letters, digits, and the four characters -, _, . and ~. These are the unreserved set defined in RFC 3986 and are safe anywhere in a URL.
They are converted to UTF-8 bytes first, then each byte is percent-encoded. An accented Latin character typically becomes two escapes and an emoji four, so a short piece of non-ASCII text can expand considerably.