Why does my redirect break when the parameter is itself a URL?
?, &, and = characters get parsed as part of the outer query string, and everything after them gets cut off or misread.This is one of the most common real-world encoding bugs, and it usually shows up in login flows, checkout redirects, and "share this page" links. Say a login page needs to send the user back to their dashboard afterward, using a parameter named returnUrl:
/login?returnUrl=https://app.example.com/dashboard?tab=billing&ref=emailHere the outer parser sees four separate parameters —
returnUrl, tab, ref — because the inner URL's own ? and & were never escaped. The login page only sees returnUrl=https://app.example.com/dashboard and silently drops ?tab=billing&ref=email.The fix is to encode the entire inner URL with encodeURIComponent() before appending it as a value: /login?returnUrl=https%3A%2F%2Fapp.example.com%2Fdashboard%3Ftab%3Dbilling%26ref%3Demail. Now the outer parser sees exactly one returnUrl parameter, and the receiving code decodes it once to get the original, complete address back.
A five-step checklist for a broken link
When a URL isn't behaving — a parameter is missing, garbled, or the wrong page loads — work through these steps roughly in order:
- 1. Parse the full URL first. Break it into protocol, host, path, query, and fragment before touching anything else. Sometimes the "missing" parameter is actually sitting inside the fragment (after
#), which most servers never see at all — that's a routing issue, not an encoding one. - 2. Decode each parameter value once and read it. If it still contains
%XXsequences or a literal%25, you're looking at a double-encoded value — decode again (or use a multi-layer/nested decode) to reach the real content. - 3. Check for un-escaped structural characters. An inner
&,=, or?that wasn't encoded will fracture the query string into extra parameters you didn't intend, exactly like thereturnUrlexample above. - 4. Confirm which encoding function produced the value. A single-parameter value should come from
encodeURIComponent; a whole address fromencodeURI. Mixing them up is the single most frequent root cause. - 5. Check the userinfo field. If the parsed host shows a username or password before the
@, credentials are embedded directly in the link — flag it before you paste that URL into a ticket, log, or chat message.
The "+" vs "%20" trap in form-encoded data
application/x-www-form-urlencoded data (HTML form submissions), a literal + means a space, and so does %20 — but outside that specific context, + is just a plus sign. Mixing the two conventions produces spaces that silently turn into plus signs, or plus signs that get read as spaces.This shows up often when a value is copied from a form submission log into a hand-built API request, or vice-versa. A search box that submits via a standard HTML form will often send a space as +; a JSON API endpoint or a manually built query string almost always expects %20 instead. If your debugging tool decodes + as a literal plus sign when the source actually meant "space" (or the reverse), the field will look subtly wrong — often only for multi-word values, which makes it easy to miss in a quick test with single-word data.
| Context | Space encodes as | Literal "+" encodes as |
|---|---|---|
| encodeURIComponent (JS default) | %20 | %2B |
| application/x-www-form-urlencoded | + (or %20) | %2B |
When testing a suspicious value, try toggling a "form encoding" option in a decode/encode tool and compare both outputs — if one interpretation produces a sensible sentence and the other produces broken words with stray plus signs, you've identified which convention the original data used.
returnUrl that unexpectedly points to a different domain than expected is easier to catch once the host is broken into parts instead of read as one long string.Paste a broken URL and see it parsed into every part, decode nested layers in one pass, and test encodeURIComponent vs encodeURI side by side — free and entirely in your browser.
Try the free URL Encode / Decode tool →