URL Query Encoding: Plus Signs, Spaces and %2B
Sending text containing plus signs + (such as Base64 signatures a+b/c== or phone numbers +86) in URL query parameters often causes servers to decode them as spaces ' '. This article explains form-encoding history and %2B percent-encoding defenses.
1. Problem: plus signs silently turning into spaces in URL queries
When transmitting data with + characters (like Base64 string eyJ+...), sending ?token=eyJ+... unencoded results in servers decoding the parameter as eyJ ... (replacing + with a space). This stems from the legacy application/x-www-form-urlencoded spec.
2. Minimal reproduction: raw plus sign vs %2B encoded parsing comparison
The comparison below illustrates how plus signs behave differently during server-side decoding:
/* Scenario 1: sending raw plus sign */
// URL: https://api.example.com/auth?phone=+8613800000000
// Server decodeURIComponent("+8613800000000") -> " 8613800000000" (plus sign becomes space!)
/* Scenario 2: explicitly encoding plus sign as %2B */
// URL: https://api.example.com/auth?phone=%2B8613800000000
// Server decodeURIComponent("%2B8613800000000") -> "+8613800000000" (plus sign preserved!)3. Root cause: conflict between HTML form encoding and RFC 3986
Under traditional HTML form encoding (application/x-www-form-urlencoded), spaces are encoded as +. When server decoders process query strings under form rules, they automatically replace + with spaces. In standard RFC 3986, spaces encode as %20 and + is a reserved character that requires percent-encoding as %2B to be read literally.
4. Recommendation: use %2B escaping and modern URLSearchParams APIs
1. After calling encodeURIComponent, append .replace(/+/g, '%2B') to force plus sign percent-encoding. 2. Use the modern JavaScript URLSearchParams API, which manages plus sign and space encoding automatically. 3. Adopt URL-Safe Base64 encoding for payload strings.
5. Complete code: safe plus sign encoder and form-compatible decoder
This TypeScript snippet demonstrates proper + handling for query parameters, preventing server-side space misinterpretation.
function safeEncodeQueryParam(paramValue: string, useFormUrlEncodedMode = false): string {
if (useFormUrlEncodedMode) {
return paramValue.replace(/%/g, "%25").replace(/\+/g, "%2B").replace(/ /g, "+");
}
return encodeURIComponent(paramValue).replace(/\+/g, "%2B");
}
function decodeQueryParam(encodedValue: string, isFormUrlEncoded = false): string {
if (isFormUrlEncoded) {
return decodeURIComponent(encodedValue.replace(/\+/g, " "));
}
return decodeURIComponent(encodedValue);
}
const base64Data = "data+key==";
const encoded = safeEncodeQueryParam(base64Data, true);
console.log(encoded); // "data%2Bkey%3D%3D"
console.log(decodeQueryParam(encoded, true)); // "data+key==" (plus sign successfully restored)6. Incorrect approaches
Appending raw Base64 strings to query parameters without encoding. Writing naive str.replace(' ', '+') server workarounds that mistakenly corrupt legitimate spaces.
7. Boundaries: POST Body vs Query parameter decoder differences
Backend frameworks (like Java Spring or Express body-parser) may handle + and %20 differently between POST Bodies and URL Query parameters.
8. Verification
Run end-to-end API tests with Base64 values containing +, %, &, =, spaces, and missing padding. Assert strict equality after server decoding, including repeated and empty parameters.
9. FAQ
Why does encodeURIComponent('+') return + instead of %2B? Per RFC 3986, + is a reserved character allowed in URIs, so encodeURIComponent leaves it unescaped by default.
Should spaces in URLs use %20 or +? Standard URL queries prefer %20; form submissions use +.
What is the safest way to pass Base64 strings in URLs? Use URL-Safe Base64 (replacing + with -, / with _, and stripping = padding).
10. Summary
Plus signs in URL queries easily turn into spaces during server decoding. Explicitly encoding + as %2B or using URL-Safe Base64 provides a foolproof defense.
Sources and further reading
Specifications and references for checking the article’s technical details.
- URL Living Standard
WHATWG
Related articles
encodeURI vs encodeURIComponent: When to Use Each
Compare JavaScript encodeURI vs encodeURIComponent for RFC 3986 reserved characters like ?, =, /, &, detailing rules for full URLs, paths, and queries.
Best practicesXSS Defense: URL vs HTML Encoding
Analyze HTML text, attribute, JS, and URL sink differences (<a href>), clarifying why URL encoding cannot replace XSS defense with multi-layered rules.
TroubleshootingFix btoa Errors: UTF-8, Chinese Text and Emoji
Analyze window.btoa Latin-1 (ASCII 0-255) limits, providing TextEncoder-based UTF-8 Unicode Base64 encoding while clarifying Base64 is not encryption.
Keep exploring
Use the related browser tool to test the workflow with your own sample data.
Open related tool