A space in a URL is encoded as %20, and that form is valid in every part of the URL. A + stands for a space only in a query string that is read as application/x-www-form-urlencoded (the format HTML forms use), so ?q=red+shoes usually means "red shoes", while /red+shoes in a path means a literal plus.
Both forms exist because two standards cover two different jobs. RFC 3986 defines the generic URL syntax, and it only knows percent-encoding. The HTML form format, now defined in the WHATWG URL Standard, added the shortcut of writing a space as +. Most bugs appear when one side of a system writes one format and the other side reads the other.
What RFC 3986 says about spaces
RFC 3986 splits ASCII characters into groups:
- Unreserved: letters, digits,
-,.,_,~. These never need encoding. - Reserved:
: / ? # [ ] @(general delimiters) and! $ & ' ( ) * + , ; =(sub-delimiters). These have special meaning in some positions. - Everything else, including the space, must be percent-encoded as
%plus two hex digits of the byte.
A space is byte 0x20, so the generic form is %20. Note that + is in the reserved list. The RFC does not give it the meaning "space"; that meaning comes only from the form-encoding rules. The RFC also asks producers to use uppercase hex digits, so %2B rather than %2b.
Where + means a space
The WHATWG URL Standard defines the application/x-www-form-urlencoded serializer and parser. The serializer writes byte 0x20 as +. The parser does the reverse and turns every + in a name or value into a space before percent-decoding. The same format leaves only ASCII letters and digits plus *, -, . and _ unencoded; everything else, including + and ~, is percent-encoded.
This is why the same query string can be read two ways:
| Raw query | Form-style parser (URLSearchParams, PHP $_GET) |
Plain percent-decoding (decodeURIComponent) |
|---|---|---|
q=a+b |
a b |
a+b |
q=a%20b |
a b |
a b |
q=a%2Bb |
a+b |
a+b |
q=a+b%2Bc |
a b+c |
a+b+c |
%20 gives the same answer in both columns. That is the main reason to prefer it when you build links by hand.
What common functions produce
Each language ships one function per convention. The outputs below were produced by running each function on the input a b+c:
| Language / function | Output | Space becomes |
|---|---|---|
JavaScript encodeURIComponent() |
a%20b%2Bc |
%20 |
JavaScript URLSearchParams.toString() |
a+b%2Bc |
+ |
PHP rawurlencode() |
a%20b%2Bc |
%20 |
PHP urlencode() |
a+b%2Bc |
+ |
PHP http_build_query() (default) |
q=a+b |
+ |
Python urllib.parse.quote() |
a%20b%2Bc |
%20 |
Python urllib.parse.quote_plus() / urlencode() |
a+b%2Bc |
+ |
Java URLEncoder.encode(s, UTF_8) |
a+b%2Bc |
+ |
Every function encodes a literal plus as %2B, so a round trip within one convention is safe. Trouble starts when you mix them.
// Same input, two conventions
encodeURIComponent("hello world"); // "hello%20world"
new URLSearchParams({ q: "hello world" }).toString(); // "q=hello+world"
// Reading back
new URLSearchParams("q=a+b%2Bc").get("q"); // "a b+c"
decodeURIComponent("a+b%2Bc"); // "a+b+c" (plus is NOT turned into a space)
<?php
echo urlencode("a b"); // a+b
echo rawurlencode("a b"); // a%20b
echo urldecode("a+b"); // a b
echo rawurldecode("a+b"); // a+b
Four mistakes that turn + into a bug
- Decoding a form query with
decodeURIComponent. The plus signs stay as plus signs, so "red+shoes" reaches your search box unchanged. UseURLSearchParamsor replace+with a space before decoding. - Leaving a literal + unencoded. A phone number such as
+15551234567or a value likeC++sent as?q=C++arrives asC(C and two spaces) on a PHP or form-style backend. Encode it as%2B. - Using + in a path.
/files/annual+report.pdfasks the server for a file whose name contains a plus. If the file is named "annual report.pdf", the correct path is/files/annual%20report.pdf. - Encoding twice. If
+or%20is encoded a second time you get%2Bwhere a space was meant, or%2520. See double URL encoding (%2520) for how to spot and fix that.
Which form should you use?
- Paths: always
%20. Better still, avoid spaces in paths. Google's URL structure guidance recommends hyphens to separate words. - Query strings you build by hand:
%20is the safest choice because it decodes the same way under both parsers. - Query strings built by a library: let the library decide.
URLSearchParamsandhttp_build_query()write+, and their matching parsers read it back correctly. - Literal plus signs: always
%2B. - Fragments and other non-query parts:
%20only; the form rules do not apply there.
A practical rule for SEO work: choose one encoding for every internal link, sitemap URL and canonical tag, and keep it. The two strings ?q=a+b and ?q=a%20b may show the same page, but they are different URLs to a crawler. For the wider picture of how encoding affects crawling, read the guide to URL encoding and Google indexing.
Reading spaces back on the server
Most server frameworks read the query string with form rules, so both + and %20 arrive as a space. PHP's $_GET, Python's urllib.parse.parse_qs() and Java's URLDecoder.decode() all turn a+b%2Bc into a b+c. Problems appear where the query is decoded by hand:
- Custom routers that call
decodeURIComponentorrawurldecodeon the raw query keep+as a plus. - Path parameters are not form-encoded. A route such as
/tags/{name}receivesc++literally from/tags/c++, which is usually what you want. - Logs and analytics often show the raw, undecoded query. Seeing
+in a log does not mean the application received a plus sign.
If you control both ends, test with a value that contains a space and a plus, such as a b+c, and confirm the server receives exactly that.
How to check a URL quickly
Paste the full address into the URL parser and query string decoder. It splits the URL into scheme, host, path, query and fragment and shows each parameter decoded with form rules, so you can see at once whether a + will arrive as a space. For single strings, the universal encoder decoder has both URL-encode and URL-decode operations.
Summary
%20 is the general, always-safe encoding for a space. + is a form-encoding shortcut that is valid only in query strings read by a form-style parser. Encode literal plus signs as %2B, keep spaces out of paths, and use one convention across your links. If parameters still arrive mangled, check for UTM encoding mistakes and HTML entities inside href attributes, the two other common sources of broken query strings.
Frequently asked questions
Is %20 or + correct for a space in a URL?
%20 is correct everywhere in a URL. A + means a space only inside a query string that is parsed as application/x-www-form-urlencoded, which is what HTML forms, URLSearchParams, PHP and most server frameworks do. In a path, + is a literal plus sign.
How do I send a real plus sign in a query parameter?
Encode it as %2B. Functions such as encodeURIComponent, URLSearchParams, PHP rawurlencode and Python quote all do this for you. If you leave a raw + in the value, a form-style parser will turn it into a space.
Does Google treat %20 and + as the same URL?
They are different strings, so you should not rely on them being merged. Pick one form for your internal links and canonical tags and use it consistently. Google's URL structure guidance asks that reserved characters be percent-encoded and recommends hyphens rather than spaces in paths.
Split a URL into scheme, host, path, query and fragment, and decode every parameter.
Open the URL Parser