When do %20 and + mean the same thing in a URL?
One copied search URL uses %20 for spaces; another uses +. Replacing every plus sign with a space seems tempting until a search for C++ loses both plus signs. The missing piece is context: generic URI syntax and form-style query parsing do not assign every character the same meaning.
Each percent escape represents a byte
A percent escape contains % followed by two hexadecimal digits representing one byte. A character can need several bytes. The Japanese character あ becomes E3 81 82 in UTF-8, so its encoded form is %E3%81%82.
Try one UTF-8 character
%E3 alone is not the complete UTF-8 sequence. decodeURIComponent throws URIError for malformed escapes or invalid UTF-8. Removing percent signs is not an encoding repair; trace which bytes and character encoding the producer used.
encodeURIComponent("あ")
// "%E3%81%82"
decodeURIComponent("%E3%81%82")
// "あ"
Form parsing gives + its space meaning
The generic URI rules in RFC 3986 do not make every plus sign a space. An application/x-www-form-urlencoded parser replaces + with a space before percent-decoding. URLSearchParams uses this form convention.
Compare the operations
To send C++ as a query value, give the unencoded value to URLSearchParams rather than concatenating it into a query string. It will encode the literal plus signs as %2B.
| Operation | Input | Result |
|---|---|---|
| decodeURIComponent | a+b | a+b |
| Read q with URLSearchParams | q=a+b | a b |
| Read q with URLSearchParams | q=C%2B%2B | C++ |
| Read q with URLSearchParams | q=C++ | C followed by two spaces |
Pass values as components, not as a whole encoded URL
In a query, & and = can separate names from values. The same characters inside a value need encoding so they are not mistaken for separators. Calling encodeURIComponent on an entire URL also encodes its scheme and slashes, destroying its intended structure.
Build a value containing plus, space and ampersand
Pass the original value to set. If you pass C%2B%2B instead, the percent signs are encoded as %25. Also remember that a slash separating path segments has a different role from a slash inside a query value.
const u = new URL("https://example.com/search");
u.searchParams.set("q", "C++ & coffee");
u.href;
// "https://example.com/search?q=C%2B%2B+%26+coffee"
u.searchParams.get("q");
// "C++ & coffee"
Do not repeatedly decode until a URL looks readable
%25 represents a literal percent sign. Decoding %252F once yields %2F; decoding that result yields /. Whether a second pass is correct depends on the input contract. An extra pass can turn data into a separator and change the value’s meaning.
Watch each layer
If a producer was supposed to encode a slash once, %252F suggests double encoding. But the intended data could also have been the literal text %2F. Inspect how the value was assembled and how many decoding steps the receiver expects; the final spelling alone cannot settle that question.
decodeURIComponent("%252F")
// "%2F"
decodeURIComponent("%2F")
// "/"
A query name can occur more than once
A query such as tag=php&tag=javascript contains two pairs. Turning it straight into a simple name-to-value dictionary can lose one. URLSearchParams preserves multiple pairs: get returns the first matching value, while getAll returns them all. The receiving application decides what multiple values mean.
What the URL tool can show
Online Tools URL Parser reads complete HTTP and HTTPS URLs through the browser URL API and displays query pairs as separate rows. It does not visit the destination to check how that site responds. Rebuilding can normalize spelling, including spaces becoming +, so it is not a way to preserve the exact text of a signed URL.
Compare values using an example.com URL
Paste https://example.com/search?q=C%2B%2B&tag=php&tag=javascript into URL Parser and Query Editor. It exposes C++ and both tag values. For an isolated component, try the URL mode in Encoder / Decoder; its decodeURIComponent operation does not change + to a space.
Related reading and specifications
See Text encoding basics for characters and bytes, and Base64 versus Base64url for another URL-friendly representation. The references are RFC 3986 §§2.1–2.4, WHATWG URL Standard §5, and ECMAScript decodeURIComponent.