ToolSink
Back to blog

URL Encoding Explained: The Rules and the Bug Everyone Hits Eventually

ToolSink Team

URL Encoding Explained: The Rules and the Bug Everyone Hits Eventually

Those %20s and %3Fs in a URL aren't random — they're percent-encoding, the mechanism that lets URLs safely carry characters that would otherwise break their structure or fall outside the ASCII character set URLs are restricted to per RFC 3986.

How percent-encoding actually works

  1. Identify a character that's unsafe or structurally reserved
  2. Convert it to its byte value (typically UTF-8)
  3. Represent each byte as two hex digits
  4. Prefix with %

A space (ASCII decimal 32, hex 20) becomes %20. This is mechanical and consistent — the same character always encodes to the same value.

Reserved vs. unsafe characters — a real distinction

Reserved characters have structural meaning in a URL: ? starts a query string, & separates parameters, = separates a key from its value, / separates path segments, # marks a fragment. These only need encoding when they appear as data rather than as structure — encoding the & that separates two query parameters would break the query string, but a literal & inside one parameter's value must be encoded, or it gets misread as a new parameter starting.

This distinction is the source of a real, common bug: encoding an entire URL (including its structural characters) versus encoding just a piece of data that will be inserted into a URL are different operations, and using the wrong one either breaks the URL or fails to protect the data.

The bug: encodeURI() vs encodeURIComponent()

In JavaScript, these are not interchangeable, and mixing them up is a genuinely common source of broken links:

const url = "https://example.com/search?q=coffee & tea";

encodeURI(url);
// "https://example.com/search?q=coffee%20&%20tea"
// Leaves structural characters (: / ? &) alone - use this on a FULL url

encodeURIComponent("coffee & tea");
// "coffee%20%26%20tea"
// Encodes EVERYTHING including & = ? - use this on a single VALUE
// going into a query parameter

The practical rule: use encodeURIComponent() on individual values you're inserting into a URL (a search term, a parameter value), and never on a complete URL — it'll also encode the :// and ? you need intact. Use encodeURI() only when you have a full URL and want to leave its structure alone while still encoding unsafe characters within it. Getting this backwards is the most common cause of a query parameter either breaking the URL or arriving at the server with the wrong value.

Python has the equivalent distinction:

from urllib.parse import quote, quote_plus, urlencode

quote_plus("coffee & tea")        # "coffee+%26+tea" - space as +, for form data
quote("coffee & tea")             # "coffee%20%26%20tea" - space as %20, for URL paths
urlencode({"q": "coffee & tea"})  # "q=coffee+%26+tea" - full query string, handles the key too

Reference table

CharacterDescriptionEncoded
(space)Space%20
!Exclamation%21
"Double quote%22
#Hash%23
$Dollar%24
%Percent%25
&Ampersand%26
'Single quote%27
( )Parentheses%28 %29
*Asterisk%2A
+Plus%2B
,Comma%2C
/Forward slash%2F
:Colon%3A
;Semicolon%3B
=Equals%3D
?Question mark%3F
@At sign%40
[ ]Brackets%5B %5D

Note: in application/x-www-form-urlencoded data (standard form submissions), a space is conventionally encoded as + rather than %20 — this is why quote_plus and quote differ above, and why you'll see both conventions depending on context.

URL structure and SEO, briefly

  • Keep slugs simple and readable (/blog/url-encoding-guide over /blog/post?id=12345)
  • Use hyphens for word separation in slugs — search engines parse hyphens as word boundaries more reliably than other separators, and hyphens never need encoding in the first place
  • Keep core paths free of characters that require encoding — reserve encoding for actual dynamic parameter values, not the path structure itself
  • Stick to lowercase in paths — some servers are case-sensitive, and inconsistent casing creates accidental duplicate URLs

Quick encode/decode without writing code

For a one-off check, ToolSink's URL Encoder/Decoder handles both directions directly in the browser — useful for quickly checking what a messy tracking URL or query string actually contains.

Conclusion

Percent-encoding itself is mechanical and simple; the actual skill is knowing whether you're encoding a full URL (preserve structure, use encodeURI) or a single value going into one (encode everything, use encodeURIComponent). That distinction, not the reference table, is where most real bugs come from.