URL Encoding Spaces: %20 vs +
A space can't appear literally in a URL, so it gets encoded — but there are two encodings you'll encounter: %20 and +. They look like alternatives, but they apply in different contexts and mixing them up causes subtle bugs.
The rule
%20— percent-encoding defined in RFC 3986. Valid everywhere in a URL: path, query string, fragment. Always correct.+— shorthand for space, valid only in query strings when usingapplication/x-www-form-urlencoded(HTML form encoding). Incorrect in URL paths.
/search/my%20query ✅ path — use %20
/search/my+query ❌ path — + is a literal plus sign here
/search?q=my+query ✅ query string (form encoding) — + means space
/search?q=my%20query ✅ query string — %20 always works
Why + works in query strings
HTML forms use application/x-www-form-urlencoded by default. In this encoding, spaces become + and + signs become %2B. Web servers and frameworks are aware of this convention and decode + as a space when parsing query parameters.
If you're building a URL by hand rather than submitting a form, prefer %20 — it's unambiguous and correct everywhere.
Where bugs happen
Server-side decoding mismatch. A server that decodes query params with a strict RFC 3986 decoder may leave + as a literal plus instead of a space. This is common when mixing platforms (e.g. a Python backend and a JavaScript frontend that used different encoding functions).
Double encoding. Encoding an already-encoded URL encodes the % itself: %20 becomes %2520. The result looks like a real URL but decodes to %20 (the literal string, not a space).
JavaScript functions
// encodeURIComponent — uses %20, RFC 3986 safe
encodeURIComponent("hello world") // "hello%20world"
// URLSearchParams — uses +, form-encoding
new URLSearchParams({ q: "hello world" }).toString() // "q=hello+world"
Use encodeURIComponent for individual path segments or when in doubt. Use URLSearchParams when building query strings for form submissions or fetch bodies.
See which characters need URL encoding for the full picture, and try encoding and decoding strings instantly with the URL encoder/decoder.
Got a config file to check?
Open the config toolkit →