Why URL Encoding Trips Up So Many Developers
You build a search feature. User searches for "C++ tutorial." Your URL becomes /search?q=C++ tutorial and it arrives at the server as q=C tutorial — the + signs became spaces, and whatever was there got lost. Or you redirect to a URL that contains another URL as a parameter and your redirect breaks at the first / or ? in the nested URL. This is URL encoding failing, and it happens everywhere.
Why URLs need encoding
A URL has a defined syntax. Characters like /, ?, &, =, and # are structural — they separate the scheme from the host, the path from the query string, parameters from each other. If your data contains any of these characters, they'll be misinterpreted as structure rather than content.
The solution is percent-encoding: replacing special characters with a % followed by the character's hexadecimal ASCII value. Space becomes %20, & becomes %26, + becomes %2B.
The characters that need encoding
RFC 3986 defines which characters are "safe" in a URL (unreserved) and which have structural meaning (reserved). Unreserved characters — letters, digits, -, _, ., ~ — can appear in URLs without encoding. Everything else should be encoded when used as data.
The characters that cause the most problems in practice:
- Space →
%20(or+in query strings, which causes confusion) &→%26— separates query parameters, so literal ampersands in values must be encoded=→%3D— separates keys from values in query strings+→%2B— treated as space in query strings (application/x-www-form-urlencoded), so literal plus signs need encoding#→%23— marks the fragment identifier; everything after it is client-only/→%2F— path separator; must be encoded when appearing in path segments or query values?→%3F— starts the query string- Non-ASCII characters — must be UTF-8 encoded first, then percent-encoded. "café" in a URL becomes
caf%C3%A9.
encodeURI vs encodeURIComponent
JavaScript developers will recognize these two functions, which encode differently and for different purposes:
encodeURI() is for encoding a complete URL. It leaves the structural characters (/, ?, &, =, #, :) alone because they're needed for the URL to function. Use this when you have a full URL with structure that should be preserved.
encodeURIComponent() is for encoding a single value — a query parameter value, a path segment. It encodes everything that isn't an unreserved character, including /, ?, and &. Use this for any user-provided or dynamic value you're inserting into a URL.
The common mistake: using encodeURI() on a parameter value that contains & or =. Those characters won't be encoded, and they'll corrupt your query string structure.
The double-encoding trap
Double-encoding happens when you encode something that's already encoded. A URL that already contains %20 gets encoded again, turning it into %2520 (because % encodes to %25). At the other end, the server decodes once and sees %20 as a literal string rather than a space.
This typically happens in multi-tier systems: the application encodes a URL, then a proxy or framework encodes it again. Or a developer calls an encoding function on a value that was already stored encoded.
The fix: only encode once, as late as possible (right before the URL is used). If you receive a URL from an external source, decode it first before manipulating it, then re-encode before using it.
Query strings vs form data
There are two encoding schemes in common use:
Percent-encoding (RFC 3986): spaces become %20, plus signs become %2B.
application/x-www-form-urlencoded: spaces become +, plus signs become %2B. This is what HTML forms use by default.
Both encode non-standard characters with percent notation, but they differ in how they handle spaces. Mismatching these at encode/decode time is a common source of broken query strings with spaces replaced by plus signs.
Practical encoding scenarios
Passing a URL as a query parameter: Always encode with encodeURIComponent. Every structural character in the nested URL needs to be escaped.
const redirectTo = encodeURIComponent('https://example.com/page?id=1&name=foo');
const link = '/auth/callback?redirect=' + redirectTo;
Building an API request with user input: Encode each parameter value separately with encodeURIComponent, not the whole URL string.
Reading encoded URLs in logs or debugging: Decode them first to see what values were actually sent. The URL Encoder / Decoder handles this instantly — paste an encoded URL or query string and see the original values.
URL Encoder / Decoder — Encode or decode URL strings and query parameters instantly. Handles percent-encoding and special characters.
Open Tool