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
%20and<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 &. 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&b=2, and the browser turns & back into a plain & before it reads the URL. Percent encoding belongs to the URL and entity encoding belongs to the markup.