Developer

URL encoding

Also called percent encoding. It writes a byte that can't appear in a URL as a percent sign and two hex digits, so a space becomes %20 and an ampersand inside a value becomes %26.

Which characters get encoded

A URL can only hold a limited set of ASCII characters. Anything else, including a space or any non-ASCII character, is written as a percent sign and two hex digits for each byte. Non-ASCII text becomes UTF-8 bytes first, so é turns into %C3%A9 and 中 into %E4%B8%AD. RFC 3986 sorts the rest into three groups:

  • Unreserved, never encoded: letters, digits, -, ., _ and ~.
  • Reserved, encoded only when they're data and not delimiters: : / ? # [ ] @ and ! $ & ' ( ) * + , ; =.
  • Everything else, always encoded: a space is %20 and < is %3C.

The same character can be a delimiter in one place and data in another. In ?a=1&b=2 the & separates two parameters. A search for fish & chips has to send ?q=fish%20%26%20chips, or the server reads a parameter that isn't there. RFC 3986 asks producers to write the hex digits in uppercase.

Spaces, plus signs and form data

A space is %20 in a URL, but HTML forms and URLSearchParams use a different rule, application/x-www-form-urlencoded, which writes a space as +. That's why a literal plus sign in a query value has to be %2B. If your + keeps turning into a space on the server, this is the reason.

In JavaScript, encodeURIComponent() encodes one value and leaves only letters, digits and - _ . ! ~ * ' ( ) as they are. encodeURI() is for a whole URL and keeps delimiters like /, ? and #. Using encodeURIComponent() on a full URL breaks it, and using encodeURI() on a value lets a raw & through.

Encode once, as late as you can

RFC 3986 says not to percent encode or decode the same string more than once. Encode twice and the % itself gets encoded: %20 becomes %2520, and the server decodes it to a literal %20 instead of a space. Keep raw values in your code and encode when you build the URL.

Binary data in a URL needs care too. Standard Base64 uses +, / and =, which all have a meaning in URLs, so it needs encoding or the URL-safe variant that swaps them for - and _. A 32-byte value is 44 Base64 characters with padding and 43 in Base64url without it.

Not the same as HTML escaping

HTML has its own escaping: an ampersand in markup is written as the entity &amp;. A URL inside an HTML attribute gets both, each at its own layer. In the HTML source the link ?a=1&b=2 is written ?a=1&amp;b=2, and the browser turns &amp; back into a plain & before it reads the URL. Percent encoding belongs to the URL and entity encoding belongs to the markup.

Work it out yourselfOpen the Text Encoding Converter

Sources

Back to the glossary