How URL encoding works
A URL may only contain a small set of ASCII characters, and some of them have a job: / separates the path, ? starts the query, & and = split it into parameters, # starts the fragment. Any other character, or one of these used as plain data, is written as % and two hex digits for each of its bytes in UTF-8. This is called percent-encoding (RFC 3986).
space → %20 (or + in form data) & → %26 / → %2F é → C3 A9 → %C3%A9 € → E2 82 AC → %E2%82%AC
Which characters to encode depends on what you are encoding. A single value, such as a search term, must have / ? & = # encoded too, or it would break the URL it goes into: that is encodeURIComponent in JavaScript and urlencode in PHP. A whole URL must keep them, or it stops being a link: that is encodeURI. HTML forms send spaces as + instead of %20 (application/x-www-form-urlencoded), so a + in a query string usually means a space.
Decoding reverses it: every %XX becomes a byte, and the bytes are read as UTF-8. The browser's own decodeURIComponent only says “URI malformed” when something is wrong. This page names the sequence and points at it: a % without two hex digits after it, or bytes that are not valid UTF-8, which usually means the text was encoded as Latin-1.
Example
The search term “vps hosting/€5” as one value becomes vps%20hosting%2F%E2%82%AC5: the space is %20, the slash %2F, and the euro sign, three bytes in UTF-8, becomes %E2%82%AC. With “Write spaces as +” for form data it becomes vps+hosting%2F%E2%82%AC5. Decoding r%C3%A9sum%C3%A9 gives résumé, and decoding caf%E9 fails at %E9, because that is é in Latin-1, not in UTF-8.
Questions
What is URL encoding?
URL encoding, or percent-encoding, writes characters that are not allowed in a URL, or that have a special meaning there, as % followed by two hex digits for each byte. A space becomes %20, & becomes %26 and é becomes %C3%A9. It lets any text travel safely inside a link or a query string.
What is the difference between encodeURI and encodeURIComponent?
encodeURI is for a whole address: it keeps characters with a job in a URL, such as : / ? # & =, so the link still works. encodeURIComponent is for one piece that goes inside a URL, such as a query value: it encodes those characters too, so a & in the value cannot start a new parameter. When in doubt, encode each value with encodeURIComponent and then join them.
Should a space be %20 or +?
Both are correct in different places. In the path of a URL, a space must be %20. In a query string sent by an HTML form (application/x-www-form-urlencoded), a space is written as +, and a real plus sign as %2B. Most servers read both in a query string. When decoding form data, turn on “Read + as a space”.
Why do I get “URI malformed” when decoding?
Either a % is not followed by two hex digits, for example a plain “100%” or “%G1”, or the %XX bytes are not valid UTF-8, often because the text was encoded in Latin-1 (é as %E9) or a multi-byte character was cut off. This page shows which sequence is wrong and where it is, so you can fix it.
How do I URL-decode on the command line or in code?
In JavaScript: decodeURIComponent(s), after s.replace(/\+/g, ' ') for form data. In Python: urllib.parse.unquote(s), or unquote_plus(s) for form data. In PHP: rawurldecode($s), or urldecode($s), which also turns + into a space. In a shell with Python installed: python3 -c "import sys, urllib.parse as u; print(u.unquote(sys.argv[1]))" 'r%C3%A9sum%C3%A9'.