ByteKit
Home › Blog › URL Encoding

URL Encoding: Percent-Encoding Rules and Common Mistakes

A URL is not a bag of text — it is a structured sequence of bytes in which certain characters carry meaning. The moment a value containing a space or a hash enters a URL, it must be percent-encoded or it changes the URL's structure instead of traveling as data. This article covers why percent-encoding exists, which characters to encode, encodeURI versus encodeURIComponent, and the plus-versus-%20 trap. To encode or decode a value safely, use the URL encoder.

Why URLs need percent-encoding

RFC 3986 splits characters into reserved and unreserved sets. The unreserved set — A-Z, a-z, 0-9, hyphen, period, underscore, tilde — appears anywhere without changing meaning. Everything else is reserved, the delimiters like colon, slash, question mark, hash, ampersand, and equals, or outside ASCII entirely.

Percent-encoding replaces a byte with a percent sign and two uppercase hex digits: a space becomes %20, a hash becomes %23. Non-ASCII characters are first encoded as UTF-8 bytes, then each byte is percent-encoded — é becomes %C3%A9, two bytes, not one.

The characters that must be encoded

The minimum safe rule for a query parameter value is to encode everything except the unreserved set.

The characters that cause damage when left raw are the space, which may become a plus or %20 depending on the parser; the ampersand, which starts a new parameter; the equals sign, which splits key from value; the hash, which starts a fragment; the percent sign itself, which begins an escape sequence; and the ambiguous plus sign.

One rule remains: encode bytes, and only after UTF-8 encoding. Encoding a character first and then converting produces double-encoded garbage: a value that arrives as literal text %C3%A9. The URL encoder handles the byte-level step; the UTF-8 encoder/decoder shows what bytes a string contains before they hit the wire.

encodeURI vs encodeURIComponent

JavaScript's two functions encode different scopes, and picking the wrong one is a classic bug. encodeURI is for a complete URL: it leaves the structural characters intact — colon, slash, question mark, hash, ampersand, equals, plus — because they carry meaning at the URL level.

encodeURIComponent is for a single component — a parameter value, a path segment, a cookie value. It encodes those same structural characters, because inside a component they are data, not structure. Use it when building a query string: encodeURIComponent(key) + "=" + encodeURIComponent(value).

The failure modes are visible in both directions. encodeURI leaves the hash intact, so a value containing a hash silently truncates your URL; encodeURIComponent turns the slash and question mark of a full URL into %2F and %3F, producing a URL that 404s. The rule in either language: encode components, never the separators.

The + versus %20 trap

Two specs meet in the query string. RFC 3986 percent-encodes a space as %20. HTML form submission, using application/x-www-form-urlencoded, encodes a space as a plus. Both appear in real URLs, and a parser for one format does not always accept the other.

The asymmetry is the trap: in form-encoding, both a plus and %20 decode to a space, so a literal plus must be sent as %2B or it decodes as a space — a search for 1+1 reads back as 1 1. If your value can contain a plus, encode it as %2B, or move it out of the query string.

ContextSpace sent asLiteral plus sent as
Form-encoded query (application/x-www-form-urlencoded)+ or %20%2B
RFC 3986 percent-encoding (paths, general URIs)%20+ or %2B
Path segment%20+ (literal)
Double-encoded value%2520%252B

Two habits prevent nearly all of this. First, never concatenate user data into a URL — pass it through a component-encoding function, every time. Second, when a value looks wrong at the server, decode it once by hand — %20 versus plus explains most odd values in logs. For binary or non-ASCII data, Base64 is the better transport — the Base64 encoder produces the payload, and percent-encoding is only needed if that payload must ride inside a URL.

Try it yourself

URL Encoder — percent-encode or decode any text or full URL, with a mode switch for component and form encoding.

Related tools: Base64 Encoder · UTF-8 Encoder/Decoder

← All posts