URL Encoding Edge Cases and Special Characters
Fix URL encoding bugs: double encoding, plus vs %20, Unicode in URLs, fragment handling, and encodeURI vs encodeURIComponent confusion. Complete guide.
The Problem
URL encoding errors are invisible: %20 and %2520 both look like encoded spaces, but one is correct and the other is a double-encoding bug. Servers may silently decode one layer, masking the problem until an edge case exposes it.
URL encoding bugs are some of the most subtle bugs on the web. They produce requests that almost work - the URL looks right, the server receives it, but the data is subtly corrupted. Double encoding turns spaces into %2520, Unicode characters break APIs, and the difference between encodeURI() and encodeURIComponent() has caused more production bugs than anyone can count. Here are the 5 most common URL encoding edge cases.
Common errors covered
Double encoding produces %2520 instead of %20
Server receives literal '%20' in parameter value
Filename appears as 'my%20file.pdf' instead of 'my file.pdf'
Encoding an already-encoded URL encodes the % character to %25, producing %2520 (double-encoded space). This commonly happens when both the client library and application code call encode.
Step-by-step fix
-
1
Use the URL Encoder/Decoder to decode and check for
%25sequences. -
2
If you see
%25, the string was double-encoded. - 3 Only encode raw values once - check if your HTTP library auto-encodes.
-
4
Use
new URL()andURLSearchParamswhich handle encoding automatically.
// Double encoding:
const encoded = encodeURIComponent('hello world'); // 'hello%20world'
const url = `https://api.com/search?q=${encodeURIComponent(encoded)}`;
// q=hello%2520world - double encoded!
// Encode once with URLSearchParams:
const url = new URL('https://api.com/search');
url.searchParams.set('q', 'hello world');
// url.toString() -> 'https://api.com/search?q=hello+world'
Space encoded as + vs %20 in wrong context
Server receives '+' literal instead of space
File path contains + instead of space
+ means space only in application/x-www-form-urlencoded (HTML form data). In URL path segments and most modern APIs, only %20 represents space. Using the wrong encoding corrupts the data.
Step-by-step fix
- 1 Use the URL tool to verify the correct encoding.
-
2
For path segments: use
encodeURIComponent()which produces%20. -
3
For query strings from forms:
+is acceptable (servers handle it). -
4
Use
URLSearchParamsfor query strings - it handles the encoding correctly.
// Using + in URL path - server sees literal +: const path = '/files/' + filename.replace(/ /g, '+'); // /files/my+report.pdf - server looks for 'my+report.pdf'
// Use %20 for paths: const path = '/files/' + encodeURIComponent(filename); // /files/my%20report.pdf - server resolves to 'my report.pdf'
encodeURI() vs encodeURIComponent() used incorrectly
404 Not Found - URL slashes encoded as %2F
or: Query parameters not encoded - data corruption
encodeURI() preserves URL structure characters (/ ? = & #) - use it for full URLs. encodeURIComponent() encodes everything - use it for individual parameter values.
Step-by-step fix
-
1
For full URLs: use
encodeURI()or better,new URL(). -
2
For parameter values: use
encodeURIComponent(). - 3 Test with the URL Encoder to see the encoded result.
-
4
Best practice: construct URLs with the
URLAPI, not string concatenation.
// encodeURIComponent on full URL - breaks structure:
encodeURIComponent('https://api.com/users?name=Alice')
// 'https%3A%2F%2Fapi.com%2Fusers%3Fname%3DAlice' - unusable!
// Use URL API:
const url = new URL('https://api.com/users');
url.searchParams.set('name', 'Alice & Bob');
// 'https://api.com/users?name=Alice+%26+Bob' - correct
Unicode characters in URLs cause 400 errors
400 Bad Request: invalid characters in URI
URIError: URI malformed
URLs must be ASCII. International characters (accented letters, CJK, emoji, Arabic) must be percent-encoded as their UTF-8 byte sequences. Sending raw Unicode in URLs violates RFC 3986.
Step-by-step fix
- 1 Use the URL Encoder - it handles Unicode-to-UTF-8-to-percent encoding automatically.
-
2
In code,
encodeURIComponent()correctly handles Unicode. - 3 For domain names, browsers handle Punycode conversion automatically.
- 4 Test with the HTML Entity Encoder for HTML contexts.
// Raw Unicode in URL:
fetch('https://api.com/search?q=cafe with accent')
// May work in some browsers, fails in others
// Correct Unicode encoding:
const url = new URL('https://api.com/search');
url.searchParams.set('q', 'cafe with accent');
// Produces correct UTF-8 percent encoding
Fragment identifier (#) breaks query parameters
Server doesn't receive parameters after #
API returns unexpected results - part of URL missing
The # fragment identifier is never sent to the server - browsers strip everything after # from the request. If # appears unencoded in a parameter value, it truncates the URL.
Step-by-step fix
-
1
Encode
#as%23in parameter values. -
2
Use
encodeURIComponent()which encodes#automatically. - 3 Test the actual HTTP request in DevTools Network tab to see what the server receives.
- 4 Use the URL Encoder to verify the encoded URL.
// Unencoded # truncates the URL: const url = 'https://api.com/search?color=#FF0000&size=large'; // Server only sees: /search?color= // Everything after # is treated as fragment
// Encode # in parameter values:
const url = new URL('https://api.com/search');
url.searchParams.set('color', '#FF0000');
url.searchParams.set('size', 'large');
// 'https://api.com/search?color=%23FF0000&size=large'
Debugging Approach
- 1 Paste the URL into the URL Encoder/Decoder tool and decode it to see the actual values.
- 2 Check for %25 sequences - they indicate double encoding.
- 3 Open DevTools Network tab and inspect the actual request URL sent to the server.
- 4 Compare what you encode on the client with what the server receives.
- 5 Test with edge-case values: emoji, spaces, #, &, =, and international characters.
Prevention Checklist
-
Never encode URLs by string concatenation - use the
URLandURLSearchParamsAPIs. -
Encode parameter values exactly once with
encodeURIComponent(). - Check if your HTTP library auto-encodes before adding your own encoding.
-
Use
%20(not+) for spaces in URL path segments. -
Always encode
#,&,=, and?in parameter values. - Test URLs with Unicode characters, spaces, and special characters before deploying.
Frequently Asked Questions
Why do some APIs use + for spaces and others use %20?
Historical reasons. The application/x-www-form-urlencoded format (from HTML forms) uses + for spaces. RFC 3986 (modern URL standard) uses %20. Most server frameworks accept both in query strings, but path segments only accept %20.
Should I encode the entire URL or just the parameters?
Only encode parameter values. The URL structure (//, /, ?, =, &) must stay unencoded for the URL to function. The safest approach is to use the URL constructor and searchParams.set().
How do I debug URL encoding issues in production?
Add server-side logging that shows both the raw request URL and the decoded parameter values. Compare the raw URL with the decoded values to spot encoding issues.
Related Debug Guides
Related Tools
Still stuck? Try our free tools
All tools run in your browser, no signup required.