Spaces in URLs: %20 vs +
The space character is the most common reason to URL-encode anything. And it’s also the source of one of URL encoding’s most persistent confusions: there are two valid encodings for a space, %20 and +, and the choice between them depends on which part of the URL you’re encoding.
This article covers both, when to use each, and the bugs that come from mixing them up.
The two encodings
%20 is the canonical percent-encoded space. It works everywhere in a URL — path, query, fragment, anywhere. It’s defined by RFC 3986.
+ is a legacy convention from HTML form submissions. It represents a space inside application/x-www-form-urlencoded data — which means inside query strings of forms submitted via GET, and inside POST bodies with that content type.
Both are valid URL encodings, but they live in different URL contexts.
Where each one works
| URL part | %20 |
+ |
|---|---|---|
| Path component | space | literal + |
| Query string value | space | space |
| Fragment (#) | space | literal + |
| Form POST body | space | space |
The key insight: in path components and fragments, only %20 means space. + means literal plus sign. In query strings and form bodies, both + and %20 are accepted as space by most servers.
How browsers handle it
When you type a URL with a space directly into the address bar, browsers encode it as %20:
You type: https://example.com/My Document
Browser sends: https://example.com/My%20Document
When you submit an HTML form via GET, browsers encode spaces in values as +:
<form action="/search" method="get">
<input name="q" value="hello world">
</form>
Browser sends: /search?q=hello+world
This is why you see both in the wild — browsers themselves use both conventions depending on context.
What happens when you mix them up
Using + in a path
// Bad
const url = "/users/" + encodeURIComponent("John Doe").replace(/%20/g, '+');
// "/users/John+Doe"
// Server sees: user with name literally "John+Doe"
// — not "John Doe" — because + is literal in paths
Decoding a query value that has +
// User submitted form value: "C++ programming"
// Browser encoded as: q=C%2B%2B+programming
// Note: the literal + in C++ became %2B, the space became +
// Wrong decoding (with + as literal)
decodeURIComponent("C%2B%2B+programming")
// → "C++ +programming" ← extra plus where space should be
// Right decoding (form-encoded variant: + → space)
"C%2B%2B+programming".replace(/\+/g, ' ').then(decodeURIComponent);
// → "C++ programming"
JavaScript’s decodeURIComponent doesn’t treat + as space. You have to handle that yourself or use URLSearchParams (which does).
The decoder behavior split
Different language decoders handle + differently:
| Language | Function | + becomes |
|---|---|---|
| JavaScript | decodeURIComponent | literal + |
| JavaScript | URLSearchParams | space |
| Python | unquote | literal + |
| Python | unquote_plus | space |
| PHP | urldecode | space |
| PHP | rawurldecode | literal + |
| Java | URLDecoder.decode | space |
| Go | url.QueryUnescape | space |
The pattern: functions named with “query” or “plus” treat + as space (form-encoded variant). Functions named “raw” or just URL leave + literal.
Encoding side: which function produces which
| Language | Function | Space becomes |
|---|---|---|
| JavaScript | encodeURIComponent | %20 |
| JavaScript | URLSearchParams | + |
| Python | quote | %20 |
| Python | quote_plus | + |
| PHP | urlencode | + |
| PHP | rawurlencode | %20 |
| Java | URLEncoder.encode | + |
| C# | Uri.EscapeDataString | %20 |
Which one should you use?
For new code, prefer %20 everywhere. Reasoning:
- Works in all URL contexts (path, query, fragment)
- Stricter compliance with RFC 3986
- Visually clearer —
%20obviously needs decoding;+looks like a literal - Avoids the
+-or-space ambiguity in code review
Use + when you specifically need application/x-www-form-urlencoded compatibility:
- Matching exactly what an HTML form would send
- Building a POST body with form content-type
- Working with an old API that explicitly requires it
What about literal plus signs?
If your data has a real + character (like “C++ programming”), encoding depends on context:
// In a path: + is already literal, but encoding it is safer
"/courses/C%2B%2B"
"/courses/C++" // also accepted
// In a query value: must be encoded if you want literal +
"?q=C%2B%2B+programming" // C++ programming
"?q=C+++programming" // 3 spaces and "programming" ← wrong
Quick decision tree
You’re encoding a space. Which to use?
- Path or fragment? →
%20 - Query string, mimicking a browser form? →
+ - Query string, building API request? → either; pick
%20for cross-platform safety - Don’t know what context you’re in? →
%20
You’re decoding a string. Was it form-encoded?
- From a query string or form POST body? → yes, treat
+as space - From a path? → no, treat
+as literal - Unsure? → use the “Treat + as space” toggle on our decoder and see which result looks right
Found this useful? Try the URL decoder, the URL encoder, or browse all tools.