Why Your URL Parameters Break: Fixing Encoding Bugs with Spaces, +, and & (2026)
You build a URL with a search query. It works for "hello" but breaks for "hello world" or "AT&T" or "C++". Your API returns 400 errors, redirects fail, or half your parameter vanishes.
URL encoding bugs are silent killers. They work in testing (simple inputs) then break in production (real user data with spaces, ampersands, and Unicode).
This guide shows you exactly which characters break URLs, why, and how to encode them correctly—including the notorious + vs %20 trap and double-encoding disasters.
Why URLs Break: Reserved Characters
The Problem in One Example
You want to search for: salt & pepper
Naive URL:
https://api.example.com/search?q=salt & pepper
What the server sees:
- Parameter
q=salt(truncated at the space!) & pepperinterpreted as a second parameter
Result: Your search for "salt & pepper" becomes a search for "salt".
Reserved Characters That MUST Be Encoded
These characters have special meaning in URLs. If they appear in data, they must be percent-encoded:
| Character | Meaning in URL | Encoded As |
|---|---|---|
space |
Separator | %20 (or + in query) |
& |
Parameter separator | %26 |
= |
Key-value separator | %3D |
? |
Query start | %3F |
# |
Fragment start | %23 |
/ |
Path separator | %2F |
+ |
Space (in query) | %2B |
% |
Escape character | %25 |
The rule: If these appear in your data (not as structural URL parts), encode them.
The Infamous + vs %20 Trap
Two Ways to Encode a Space
This trips up even senior developers:
| Context | Space Encoded As | Standard |
|---|---|---|
Query string (?q=...) |
+ OR %20 |
application/x-www-form-urlencoded |
Path (/foo/...) |
%20 ONLY |
RFC 3986 |
The Bug This Causes
Scenario: You encode a space as + in a URL path:
https://example.com/files/my+document.pdf
Server interprets: Literal filename my+document.pdf (with a plus sign!)
Correct:
https://example.com/files/my%20document.pdf
The Reverse Bug
Scenario: User searches for C++ (the programming language):
https://example.com/search?q=C++
Server decodes: C (two spaces! because + = space in query strings)
Correct:
https://example.com/search?q=C%2B%2B
The fix: In query strings, always encode literal + as %2B.
JavaScript: encodeURI vs encodeURIComponent
The Critical Difference
JavaScript has two encoding functions, and using the wrong one is the #1 cause of URL bugs:
| Function | Encodes | Use For |
|---|---|---|
encodeURI() |
Spaces, Unicode — but NOT & = ? / # |
Encoding a whole URL |
encodeURIComponent() |
Everything reserved, including & = ? / |
Encoding a single parameter value |
When Each Fails
Using encodeURI for a parameter value (WRONG):
const query = "salt & pepper";
const url = "https://api.com/search?q=" + encodeURI(query);
// Result: ?q=salt%20&%20pepper ← & NOT encoded, breaks!
Using encodeURIComponent (CORRECT):
const query = "salt & pepper";
const url = "https://api.com/search?q=" + encodeURIComponent(query);
// Result: ?q=salt%20%26%20pepper ← & encoded as %26, works!
The Rule
- Encoding one value (a parameter, a path segment)? →
encodeURIComponent() - Encoding a complete URL you built yourself? →
encodeURI()
99% of the time, you want encodeURIComponent().
Building URLs Correctly: URLSearchParams
Stop String-Concatenating URLs
The modern, bug-free way to build query strings:
const params = new URLSearchParams({
q: "salt & pepper",
category: "food/spices",
price: "10+"
});
const url = "https://api.com/search?" + params.toString();
// Result: ?q=salt+%26+pepper&category=food%2Fspices&price=10%2B
// All special characters correctly encoded automatically
Why this is better:
- Automatically encodes all values
- Handles
&,=, spaces, Unicode correctly - No manual encoding mistakes
- Readable code
Reading Parameters Back
const url = new URL("https://api.com/search?q=salt+%26+pepper");
const q = url.searchParams.get("q");
// q = "salt & pepper" ← automatically decoded
The Double-Encoding Disaster
How It Happens
Step 1: Frontend encodes a value:
encodeURIComponent("a&b") // → "a%26b"
Step 2: Backend receives a%26b, encodes it AGAIN before storing/forwarding:
"a%26b" → "a%2526b" (% became %25)
Step 3: When decoded once, you get a%26b instead of a&b. The value is corrupted.
How to Detect Double-Encoding
Look for %25 in your URLs where you didn't intend a literal %:
%2526= double-encoded&%2520= double-encoded space%253D= double-encoded=
The Fix
Encode exactly once. Establish clear ownership:
- Frontend encodes when building the URL
- Backend decodes on receipt, stores raw
- Backend re-encodes only when forwarding to another URL
Never blindly encode data that might already be encoded.
Unicode and International Characters
The UTF-8 Percent-Encoding
Non-ASCII characters (emoji, accents, CJK) must be UTF-8 encoded, then percent-encoded:
| Character | UTF-8 Bytes | URL Encoded |
|---|---|---|
é |
0xC3 0xA9 | %C3%A9 |
中 |
0xE4 0xB8 0xAD | %E4%B8%AD |
😀 |
0xF0 0x9F 0x98 0x80 | %F0%9F%98%80 |
JavaScript handles this automatically:
encodeURIComponent("café") // → "caf%C3%A9"
encodeURIComponent("北京") // → "%E5%8C%97%E4%BA%AC"
encodeURIComponent("hello 😀") // → "hello%20%F0%9F%98%80"
Common Unicode Bug
Problem: Server expects Latin-1 but gets UTF-8, or vice versa. Result: café becomes café.
Fix: Ensure both ends use UTF-8. Set Content-Type: text/html; charset=utf-8 and use UTF-8 encoding functions consistently.
Real-World Debugging Checklist
When a URL parameter breaks, check in order:
☐ Is the value encoded at all? (spaces, & present raw?)
☐ Right function? (encodeURIComponent for values, not encodeURI)
☐ Plus sign issue? (literal + in query = space bug)
☐ Double-encoded? (look for %25 you didn't intend)
☐ Unicode charset? (UTF-8 both ends?)
☐ Path vs query? (spaces: %20 in path, + or %20 in query)
Encoding Reference: Copy-Paste Safe
Encode a single parameter value
const encoded = encodeURIComponent(value);
Build a full query string
const qs = new URLSearchParams({ key1: val1, key2: val2 }).toString();
Decode a value
const decoded = decodeURIComponent(encoded);
Python equivalent
from urllib.parse import quote, urlencode, unquote
quote("salt & pepper") # 'salt%20%26%20pepper'
urlencode({"q": "salt & pepper"}) # 'q=salt+%26+pepper'
unquote("salt%20%26%20pepper") # 'salt & pepper'
FAQ
Q: Should I use + or %20 for spaces?
A: In query strings, both work (+ is form-encoding standard). In paths, use %20 only. When in doubt, %20 works everywhere.
Q: Why does my + sign turn into a space?
A: In query strings, + means space. To send a literal +, encode it as %2B.
Q: encodeURI or encodeURIComponent?
A: encodeURIComponent for individual values (99% of cases). encodeURI only when encoding a complete, already-structured URL.
Q: How do I know if a URL is double-encoded?
A: Look for %25 (encoded %) where you expected a single-encoded character. %2520 = double-encoded space.
Q: Do I need to encode Unicode manually?
A: No. encodeURIComponent() (JS) and quote() (Python) handle UTF-8 encoding automatically.
Conclusion
URL encoding bugs come from a few predictable mistakes:
- Not encoding reserved characters (
& = ? /in data) - Wrong function (
encodeURIvsencodeURIComponent) - The + trap (literal
+becomes space in queries) - Double-encoding (encoding already-encoded data)
- Charset mismatch (not using UTF-8 consistently)
The safe defaults:
- Use
encodeURIComponent()for values - Use
URLSearchParamsto build query strings - Encode exactly once
- UTF-8 everywhere
Follow these and your URLs will handle spaces, ampersands, plus signs, and 😀 without breaking.