Why '+' Doesn't Decode to a Space in URL Decoding — Double Decoding Explained
You've probably pasted a query string like q=hello+world — copied from a search box or a form — into a URL decoder, and seen the + come out untouched instead of turning into a space. That's not a bug. JavaScript's decodeURIComponent and decodeURI were simply never designed to handle + at all. Here's why, along with what to watch out for when decoding double-encoded values. (For why double encoding happens on the encoding side, see the double encoding (%2520) guide — this article focuses on decoding, the '+' handling, and error cases.)
1. '+' = space is really two specs blended together
URLs actually follow two overlapping conventions for representing characters, not one. RFC 3986's general percent-encoding represents a space as %20. That's the spec that encodeURIComponent, decodeURIComponent, and decodeURI operate on. application/x-www-form-urlencoded, by contrast, is a separate spec browsers use when building a query string from an HTML <form method="GET"> submission — and there, a space is represented as +. This spec was originally created for transmitting HTTP form data, not as part of general URI syntax. As a result, JavaScript's standard decoding functions know nothing about it, and simply pass a literal + straight through, treating it as an ordinary plus-sign character.
2. What this tool actually does: the '+ → space' checkbox
This site's URL Decoder internally uses only decodeURIComponent and decodeURI across all three modes (Auto, Component, Full URL). That means by default, it never touches + at all. For handling form and search-query data, there's a separate + → space checkbox — enabling it first replaces every literal + with a space using a regex, before the percent-decoding step runs on everything else. The order matters: a real '+' character that was already encoded as %2B is still in its %2B form at the replacement step, so it's untouched by it, and gets correctly restored to a literal '+' during the subsequent percent-decoding step. If you did percent-decoding first instead, %2B would already have become '+', and you'd have no way to tell it apart from a '+' that was meant to be a space.
| Input | Mode | Result | Explanation |
|---|---|---|---|
a+b%20c | Default (checkbox off) | a+b c | Only %20 becomes a space; the literal + is left as-is |
a+b%20c | + → space on | a b c | + is replaced with a space first, then %20 is also decoded to a space |
hello%2Bworld | + → space on | hello+world | %2B isn't a replacement target, so it correctly decodes back to '+' |
3. Double decoding: why one pass sometimes isn't enough
If a value somewhere was accidentally encoded twice (e.g. %2520), running this decoder once only unwinds %2520 → %20 — it doesn't reach the space. If the result still contains a sequence starting with %20 or %25, that's a sign one more decode pass is needed. This tool doesn't auto-repeat decoding, so you'll need to paste the result back into the input and click Decode again. One caveat: sometimes the original text genuinely contains a value starting with %25 by coincidence (e.g. encoding a discount rate "50%" produces 50%25), so before decoding again, it's safer to eyeball whether the result is already meaningful text.
4. What actually causes a URIError
decodeURIComponent only accepts a sequence as valid if % is followed by exactly two hex digits. A non-hex character like %GG, or a truncated sequence like %2, immediately throws URIError: URI malformed. Characters represented as multiple bytes in UTF-8 — Korean, Chinese, emoji — need all the bytes in a set to be intact, like %EC%95%88; if only part of that set arrives (say, the string got cut off mid-copy), the byte sequence is broken and throws the same error. In Auto mode, this tool automatically falls back to decodeURI when decodeURIComponent fails, but if the input is fundamentally malformed in a way that breaks both functions equally, you'll still see an error message.
5. Summary
- + = space is a form-urlencoded convention: it's a separate spec from RFC 3986 percent-encoding, so standard decoding functions don't handle it automatically.
- The '+' substitution must always happen before percent-decoding: reverse the order, and you can no longer distinguish a real '+' (encoded as %2B) from one that was meant to be a space.
- Spot double encoding by checking for leftover %XX in the result: if it's still there, decode once more.
- A URIError comes from a malformed %sequence or a broken multi-byte character: check first whether the original text was cut off midway.
FAQ
Why doesn't decodeURIComponent turn '+' into a space?
decodeURIComponent is designed to handle only RFC 3986 percent-encoding (%XX) — the '+' character was never within its scope to begin with. The '+' = space convention comes from a completely separate spec, application/x-www-form-urlencoded (used by HTML form GET submissions).
When should I turn on the URL decoder's '+ → space' checkbox?
Turn it on when decoding a query string copied from a search box or HTML form (e.g. q=hello+world). Values produced by encodeURIComponent for a REST API generally don't contain '+', so it's safer to leave it off in that case.
What happens if I decode a double-encoded value only once?
Decoding a value that was encoded twice — like %2520 — only one time leaves the literal string %20 instead of a space. To fully reverse it, you need to run the decoder twice.
When does decodeURIComponent throw a URIError?
A URIError occurs when a % isn't followed by exactly two valid hex digits (e.g. %GG), or when a sequence is cut off midway (e.g. %2). It also happens when a UTF-8 multi-byte sequence is broken — for example, a multi-byte character truncated mid-copy.