Share link, pasted: https://shop.com/search?q=red shoes&sort=price. Clicked: search for “red”, sort parameter eaten. The unencoded space and raw & split the query in two — a bug invisible in every test with single-word queries. URL encoding is boundary plumbing: %20 vs +, reserved vs unreserved, single vs double encoding. This guide fixes the five encoding bugs behind most “works with test data” link failures.
Part of the developer toolkit guide. Encode and parse with the URL encoder and URL parser.
Spaces: %20 in paths, + in forms
The #1 encoding bug: spaces mean different things per context. In URL paths and most APIs, space → %20 (red%20shoes). In HTML form bodies (application/x-www-form-urlencoded), space → +. Servers decoding form-style + in paths turn “C++” into “C ” — a real bug I have fixed twice. Rule: encode for the context you send to, and test with multi-word values containing & and #, not just “hello world”. café → caf%C3%A9 always (UTF-8 bytes, never Latin-1).
Reserved characters: encode values, never structure
| Char | Meaning unencoded | In values, send |
|---|---|---|
| & | Parameter separator | %26 |
| = | Key/value split | %3D |
| # | Fragment start (never sent!) | %23 |
| ? | Query start | %3F |
| % | Escape introducer | %25 (never double-encode) |
Encode values, not structure: ?q=red%20shoes&sort=price keeps separators literal and content encoded. Double-encoding (%2520) happens when two layers each encode — trace which layer owns encoding and make it exactly one. Debug with the parser: paste the broken URL and watch where parameters actually split.
Limits, i18n and the 2000-character cliff
Practical ceilings: ~2000 characters total (older proxies/IE truncate beyond), ~40 non-ASCII symbols before readability collapses, UTF-8 everywhere (emoji = 4 bytes each — budget URL length accordingly). Internationalized domain names punycode-encode (müller.de → xn--mller-kva.de) while paths percent-encode — different mechanisms, both required. For share links with 200+ characters of state, stop encoding and POST the payload or use a short-link store: URLs are addresses, not databases. Fragments (#section) never reach servers — don't put access tokens where only JavaScript can see them (and prefer not to put tokens in URLs at all; see token hygiene).
General guidance only. Test encoded URLs by clicking through, not just copying — intermediaries (chat apps, email clients) re-encode unpredictably.