DevSwitch.net

URL Encoding: Why Special Characters Need Percent Encoding

A glowing URL bar with percent-encoded special characters floating around it against a clean digital background.

URL encoding (also called percent encoding) swaps out characters that a browser or server can't safely put in a web address for a % sign followed by two hexadecimal digits. A space becomes %20, an ampersand becomes %26, and so on. This matters because URLs are only allowed to contain a small, specific set of characters, and anything outside that set has to be converted or the link can break, get misread, or expose your site to bugs.

What URL encoding actually does

A URL is a text string with a strict grammar. The rules come from RFC 3986, the spec that defines how URIs are structured. That grammar only permits a limited alphabet: letters (A-Z, a-z), digits (0-9), and a handful of symbols. Everything else has to be represented in an encoded form so the URL stays unambiguous from start to finish.

Think about a search query like fish & chips. If you drop that straight into a URL, two things go wrong:

  • The space can end the URL early or get mangled by systems that treat spaces as separators.
  • The ampersand already has a job in URLs. It separates parameters in a query string, so an unencoded & gets read as "here comes a new parameter" instead of literal text.

URL safe encoding fixes both by turning the string into fish%20%26%20chips, which every browser and server reads exactly the same way.

How percent encoding works

The mechanism is simple once you see it. Each character you need to encode is:

  1. Looked up as its byte value (usually its UTF-8 representation).
  2. Written in two-digit hexadecimal.
  3. Prefixed with a % sign.

Here are a few everyday examples:

Character Percent encoded Why it needs encoding
space %20 Not allowed in URLs; splits the address
& %26 Reserved as a parameter separator
? %3F Reserved; marks the start of the query
# %23 Reserved; marks a fragment identifier
= %3D Reserved; separates key from value
+ %2B Can be read as an encoded space
é %C3%A9 Non-ASCII; needs UTF-8 bytes (two of them)

Notice that é becomes two percent groups. That's because in UTF-8 it's stored as two bytes (0xC3 and 0xA9), and each byte gets its own % encoding. This is why non-English characters and emoji produce long strings of percent codes.

Case tip: Hex digits can be uppercase or lowercase (%3F equals %3f), but RFC 3986 recommends uppercase. Stick with uppercase for consistency.

Reserved, unsafe, and safe characters

Characters fall into three practical buckets, and knowing which is which tells you when encoding is required.

Unreserved (safe) characters

These never need encoding and should be left alone:

  • Letters A-Z and a-z
  • Digits 0-9
  • The four symbols - _ . ~

Reserved characters

These have special meaning in a URL's structure. They're fine when used for their intended purpose, but must be encoded when they appear as literal data. The reserved set includes:

: / ? # [ ] @ ! $ & ' ( ) * + , ; =

A / inside a path is a directory separator, so it stays. But a / inside a path segment that holds data (like the date "12/25" in /archive/12%2F25) has to become %2F, or the server reads it as an extra path level.

Unsafe characters

These url unsafe characters cause trouble because older systems, proxies, or gateways might alter them. Always encode:

  • Spaces, quotes ", and angle brackets < >
  • Curly braces { }, the pipe |, backslash \, caret ^
  • The percent sign itself, which becomes %25 so it isn't mistaken for the start of an encoding

Encoding query strings the right way

The query string (everything after the ?) is where most encoding bugs happen because it holds user-supplied values. The safe pattern is to encode each key and each value separately, then join them with the raw & and = that the URL needs as structure.

Say you want to pass the value rock & roll as a parameter called genre. The correct result is:

https://example.com/search?genre=rock%20%26%20roll

Most languages give you a helper so you don't build these strings by hand:

  • JavaScript: encodeURIComponent() for individual values, encodeURI() for a whole URL. See the MDN reference for the exact difference.
  • Python: urllib.parse.quote() and quote_plus(), documented in the Python standard library.
  • PHP: rawurlencode() (RFC 3986 style) or urlencode() (form style, spaces become +).
Use encodeURIComponent, not encodeURI, on parameter values. encodeURI leaves &, =, and ? untouched because it assumes they're structural, which corrupts any value that actually contains them.

Common encoding mistakes to avoid

A few errors show up over and over:

  • Double encoding. Encoding an already-encoded string turns %20 into %2520 (the % gets re-encoded to %25). Encode exactly once, at the point where you assemble the URL.
  • Mixing + and %20 for spaces. In query strings, + historically means a space, but in the path portion it means a literal plus. When in doubt, use %20, which is unambiguous everywhere.
  • Forgetting non-ASCII characters. Names, accents, and emoji all need UTF-8 percent encoding. Skipping this is a top cause of broken international links.
  • Encoding the whole URL blindly. If you percent-encode the :// or the domain's dots, the address stops working. Only encode the parts that hold data.

When you decode, the process runs in reverse: each % plus two hex digits is turned back into its original byte, and the bytes are read as UTF-8. That symmetry is what lets a value survive the round trip from browser to server and back unchanged.

They are the same thing. "Percent encoding" is the formal name in RFC 3986 because the method replaces unsafe characters with a percent sign followed by two hex digits. "URL encoding" is the everyday term people use for applying that same process to web addresses.

The + for space comes from an older HTML form-encoding standard used in query strings. RFC 3986 percent encoding uses %20 everywhere. Both can appear, so %20 is the safer, universally understood choice for representing a space in any part of a URL.

No. Letters A-Z and a-z, digits 0-9, and the symbols hyphen, underscore, period, and tilde are "unreserved" characters. They are always safe and should never be encoded. Encoding them anyway just makes the URL longer and harder to read without changing its meaning.

Double encoding happens when you run an already-encoded string through an encoder again, turning %20 into %2520. Fix it by encoding a value only once, right when you build the final URL. If you receive encoded data, decode it before re-encoding for a new context.

They are first converted to their UTF-8 byte sequence, then each byte is percent encoded. Because these characters use multiple bytes, they produce multiple percent groups. For example, é becomes %C3%A9, and a single emoji can expand to four percent-encoded bytes.