URL encoding, also called percent-encoding, replaces characters that a URL cannot carry with a % and two hex digits. A space in a link becomes %20, an ampersand becomes %26, and a search for café turns into caf%C3%A9. It lets URLs carry characters that would otherwise break them.
This guide explains how percent-encoding works, which characters are safe and which must be escaped, the confusing case of + versus %20 for a space, and the difference between JavaScript's encodeURIComponent and encodeURI. Mixing up those two causes many malformed URL bugs. The guide follows RFC 3986, the standard that defines URI syntax.
Why URLs need encoding
A URL has a strict grammar. Some characters have structural jobs: ? starts the query string, # marks a fragment, & separates query parameters, / divides path segments, and = pairs a key with a value. If your data contains one of these characters, putting it in raw confuses the parser. A & inside a value looks exactly like the start of a new parameter.
URLs are also limited to a small set of ASCII characters. Spaces, most punctuation and anything non-ASCII (accented letters, emoji, other scripts) cannot appear literally. Percent-encoding is the escape mechanism that lets any byte sit inside a URL without ambiguity.
How percent-encoding works
The rule is mechanical. Take the byte you need to escape and replace it with % followed by the byte's value as two hexadecimal digits. A space is byte 0x20, so it becomes %20. An ampersand is 0x26, so it becomes %26. A # is 0x23, so it becomes %23.
Space 0x20 → %20
# 0x23 → %23
& 0x26 → %26
? 0x3F → %3F
/ 0x2F → %2F
= 0x3D → %3DSo when you see %2F in a URL, it is an encoded slash that belongs to the data, not a path separator. A colon encodes to %3A. You only need to encode it when it appears inside a value, because in https: it is part of the structure.
Non-ASCII characters are first encoded to their UTF-8 bytes, and then each byte is percent-encoded. The letter é is two UTF-8 bytes, 0xC3 0xA9, so it becomes %C3%A9. That is why café turns into caf%C3%A9: one character, two percent escapes.
RFC 3986 also defines unreserved characters that never need encoding: the ASCII letters A-Z and a-z, the digits 0-9, and the four symbols - _ . ~. Every other character is either reserved (structural) or unsafe, and should be percent-encoded when it appears as data rather than structure.
Space in a URL: + vs %20
Sometimes a space is %20, and sometimes it is +. Both can be correct. It depends on *where in the URL you are* and *which encoding rules apply*.
- In the path and general URI syntax (RFC 3986), a space is always
%20. A literal+here means an actual plus sign. - In a query string encoded as `application/x-www-form-urlencoded`, the format HTML forms submit, a space is encoded as
+and a literal plus becomes%2B.
Decoders follow the same split. Form-style decoders turn + back into a space, and a strict URI decoder leaves + as a plus. If you are unsure how a + will be read, use %20 for spaces and %2B for literal pluses. Every decoder reads those the same way.
encodeURIComponent vs encodeURI
JavaScript has two encoding functions, and picking the wrong one is a very common URL bug. They differ in which characters they treat as structural and leave alone.
| Function | Purpose | Leaves reserved chars (/ ? & = #) alone? |
|---|---|---|
| encodeURI | Encode a whole URL | Yes, so the URL still works |
| encodeURIComponent | Encode a single value or component | No, it escapes them too |
encodeURI assumes it has a complete URL whose structure must stay intact, so it does *not* escape / ? & = # :. encodeURIComponent assumes it has a single piece of data, such as a query value or a path segment, that must not be mistaken for structure, so it escapes those characters too. For anything you put into a query parameter, you almost always want encodeURIComponent.
const q = "tom & jerry?";
encodeURI(q);
// "tom%20&%20jerry?" & and ? survive, WRONG inside a value
encodeURIComponent(q);
// "tom%20%26%20jerry%3F" fully escaped, safe as a value
// Building a URL: encode each value, not the whole thing
const url = `/search?q=${encodeURIComponent(q)}&page=1`;
// "/search?q=tom%20%26%20jerry%3F&page=1"// The modern, hard-to-get-wrong approach:
const url = new URL("https://api.example.com/search");
url.searchParams.set("q", "tom & jerry?");
url.searchParams.set("page", "1");
url.toString();
// "https://api.example.com/search?q=tom+%26+jerry%3F&page=1"URL encoding and JSON in URLs
Passing JSON through a URL is common: a filter object, a state blob, a config for a shareable link. JSON is full of characters that are reserved or unsafe in URLs: {, }, ", :, , and spaces. Put in raw, they corrupt the query string. The fix is to percent-encode the whole JSON string as a single component before you append it.
const filter = { tag: "news", limit: 10 };
const json = JSON.stringify(filter);
// '{"tag":"news","limit":10}'
const param = encodeURIComponent(json);
// '%7B%22tag%22%3A%22news%22%2C%22limit%22%3A10%7D'
const url = `/list?filter=${param}`;
// Decode on the other side:
const back = JSON.parse(decodeURIComponent(param));To check what an escaped URL contains, decode it instead of guessing. The encoder and decoder below run in your browser, so you can paste a URL, read the plain text, and see exactly what each parameter holds without sending it anywhere.