URL Encoding Explained: What %20 and Percent-Encoding Really Mean
Why URLs use %20 instead of spaces: percent-encoding explained, which characters are reserved vs unreserved, and when to encode query strings and paths.

URL encoding (also called percent-encoding) is the way a browser rewrites characters that a URL can’t safely carry, replacing each one with a percent sign followed by two hexadecimal digits. A space becomes %20, a # becomes %23, and an accented é becomes %C3%A9. It exists because a URL is only allowed to use a small, fixed set of ASCII characters, so anything outside that set has to be escaped into a form that survives the trip across the network.
What is URL encoding (a.k.a. percent-encoding)?
Percent-encoding is a rule from RFC 3986, the spec that defines how URLs are written. The rule is simple: take the byte you want to send, look at its value in hexadecimal, and write it as %XX. The letter space is byte 0x20, so it turns into %20. The plus sign is 0x2B, so in contexts where it needs escaping it becomes %2B.
You’ve seen this every time you copied a link with a search in it. Type “url encoding” into a search box and the address bar might show q=url%20encoding or q=url+encoding. Both mean the same space, just encoded two different ways (more on the + later).
The point of URL encoding is safe transport. A URL passes through browsers, servers, log files, and email clients, and each one has to agree on where the URL starts and ends and what its pieces are. If you dropped a raw space or a raw # into the middle of a link, one of those systems would misread it. Encoding removes the ambiguity.
Why %20 exists, the space, and other unsafe characters
A space is the classic example because URLs simply can’t contain one. The moment a browser or server hits a raw space, it assumes the URL has ended. So the space has to be encoded, and its byte value 0x20 gives you %20.
Spaces aren’t the only problem. To URL encode special characters safely, you need to escape anything that either has a structural job in a URL or falls outside plain ASCII. Common ones:
- Space ->
%20 #->%23(normally marks the start of a fragment)?->%3F(normally starts the query string)&->%26(normally separates query parameters)=->%3D(normally joins a key to its value)/->%2F(normally separates path segments)%->%25(the escape character itself, so it must escape itself)
Anything non-ASCII gets encoded through its UTF-8 bytes. The character é is two bytes in UTF-8 (0xC3 0xA9), so it becomes %C3%A9. An emoji can be four bytes and turn into four %XX pairs. This is why a link with non-English text often looks like a wall of percent signs when you copy it.
Reserved vs unreserved characters (the RFC 3986 rules, plainly)
RFC 3986 splits characters into two buckets, and knowing which is which tells you what to encode.
Unreserved characters never need encoding. They are the letters A-Z and a-z, the digits 0-9, and four symbols: hyphen -, period ., underscore _, and tilde ~. You can leave these alone anywhere in a URL, and a correct encoder should not touch them.
Reserved characters carry structural meaning: : / ? # [ ] @ ! $ & ' ( ) * + , ; =. These are the delimiters that separate a URL into scheme, host, path, query, and fragment. The subtlety is that a reserved character is only special in certain positions. A / between path segments is doing its job, so you leave it. A / that’s part of a value inside a query parameter is data, not structure, so you encode it to %2F. That single distinction is the source of most real-world encoding bugs.
Everything that is neither unreserved nor a reserved delimiter you actually want in that spot gets percent-encoded. If you’re fuzzy on why a byte maps to a specific two-digit code, it helps to be comfortable with how text becomes bytes in the first place, which is the same territory as UTF-8 vs ASCII.
What actually happens to a query string
The query string is the part after the ?, and it’s where most encoding decisions happen. It’s a list of key=value pairs joined by &. So the characters = and & are structural there, and if a value itself contains an = or &, that value has to be encoded or the whole thing falls apart.
Say you’re building a search link where the query is salt & pepper. If you write ?q=salt & pepper raw, the server sees a parameter q equal to salt , a stray pepper, and a space that shouldn’t exist. Encode the value and it becomes ?q=salt%20%26%20pepper, which reads back as the single value salt & pepper. The & inside the value is now %26, so it can’t be mistaken for a separator.
One historical quirk lives here. In query strings that come from HTML form submissions, a space is often written as + instead of %20. That’s the application/x-www-form-urlencoded format. Both decode to a space in a query, but + only means space in the query part. In a path, + is a literal plus. This mismatch trips people up constantly.
encodeURIComponent vs encodeURI, the JavaScript gotcha
JavaScript gives you two encoding functions, and picking the wrong one is a common bug. encodeURI is meant for a whole URL and deliberately leaves the structural characters (: / ? # & =) alone so the URL still works. encodeURIComponent is meant for a single piece (one query value, one path segment) and encodes those structural characters too.
Use encodeURIComponent when you’re inserting a value into a URL:
const term = "salt & pepper";
const url = "https://example.com/search?q=" + encodeURIComponent(term);
// https://example.com/search?q=salt%20%26%20pepper
If you used encodeURI there, the & in salt & pepper would survive unencoded and split your parameter in two. The rule of thumb: encodeURIComponent for the parts you’re plugging in, encodeURI only when you have a complete URL string you want to make safe without breaking its structure. In practice you reach for encodeURIComponent far more often.
Neither function encodes the unreserved characters, and both leave - _ . ! ~ * ' ( ) alone, which is fine for URLs but occasionally surprising if you expected everything non-alphanumeric to be escaped.
URL encoding vs Base64 vs UTF-8, how they relate
These three get lumped together, but they solve different problems.
UTF-8 is a character encoding: it turns text into bytes. It’s the foundation, because percent-encoding works on bytes, and UTF-8 is what decides that é is the two bytes C3 A9. UTF-8 doesn’t make anything URL-safe on its own; it just defines the byte values.
URL encoding takes those bytes and makes them safe to sit inside a URL, one %XX per byte. It’s a transport wrapper for the URL context specifically.
Base64 also makes binary data safe for text channels, but it does it differently: it repacks bytes into a 64-character alphabet, growing the data by about a third. You’d use Base64 to stuff an image or a token into text, not to escape a space in a link. If you want the full picture of that scheme, see what is Base64 encoding.
A useful way to keep them straight: UTF-8 decides the bytes, URL encoding escapes those bytes for a URL, and Base64 is a separate packaging for arbitrary binary. And none of the three is hashing. Encoding is always reversible by design, which is the opposite of what hashing does, where you can’t get the input back.
Double-encoding: the bug that breaks links
Double-encoding happens when you encode a string that was already encoded. The escape character % gets escaped again, so %20 (an encoded space) becomes %2520, because the % itself turns into %25. Now the URL carries a literal %20 as text instead of a space, and the link is wrong.
You see this when a value passes through two layers that both “helpfully” encode it. A backend encodes a redirect URL, then a template or another library encodes the whole thing again. The user clicks and lands on a page whose address is full of %25, or gets a 404 because the server decoded once and still saw %20 sitting in the path.
The fix is to encode exactly once, at the boundary where you insert raw data into the URL, and never re-encode a string you know is already encoded. When you’re debugging a broken link, look for %25 in it. That’s almost always the fingerprint of double-encoding.
Encode or decode a URL safely
The safest way to check your work is to encode or decode a URL and read the result yourself, rather than guessing byte values. Our upcoming Base64 and URL encode tool will let you paste a messy link, decode it to plain text, or take a value and encode it before dropping it into a query string.
Until that one ships, our existing utilities like the JSON formatter are handy when a URL-encoded value turns out to be a JSON blob you need to read.
FAQ
What does %20 mean in a URL?
%20 is a percent-encoded space. The space character has the byte value 0x20 in hexadecimal, and URL encoding writes any escaped byte as a percent sign plus its two-digit hex code. So %20 is simply “a space, made safe to sit inside a URL.”
Do I always need to URL encode a query string?
You need to encode the values, not the structure. The ?, &, and = that hold the query together should stay as they are, but any value that contains one of those characters, or a space, or non-ASCII text, must be encoded so it isn’t mistaken for structure. When in doubt, run each value through encodeURIComponent.
Why does a space sometimes show as + instead of %20?
The + for space comes from the application/x-www-form-urlencoded format used by HTML form submissions. In the query part of a URL, both + and %20 decode to a space. But + only means space in a query; in a path it stays a literal plus, so %20 is the safer, more universal choice.
Is URL encoding the same as encryption?
No. URL encoding is fully reversible and hides nothing. Anyone can decode %C3%A9 back to é in seconds. It exists for safe transport, not secrecy, so never rely on it to protect a token or a password.