Why Double Encoding (%2520) Happens — encodeURIComponent and the '+' Trap
If you've worked with URL parameters, you've probably run into a strange value with a doubled-up percent sign, like %2520. That's not a typo — it's a classic bug that happens when you encode an already-encoded string. It's especially common in URLs that pass through multiple layers — encoded once on the frontend, then again in a backend proxy or library.
1. How %20 turns into %2520
Encode a single space with encodeURIComponent and you get %20. Since the % character itself also carries special meaning in a URL, if you encode this %20 string again without checking, the % gets converted to its own percent-encoded form, %25, leaving %2520 as the end result. If the server decodes this value only once, what's left isn't a space but the literal string %20 — you need to decode it twice to get back to the original space. In this state, search terms or filenames can end up stored or retrieved in ways that don't match what was originally intended.
| Step | Value | Explanation |
|---|---|---|
| Original | hello world | Raw text with a space |
| 1st encoding | hello%20world | Space correctly converted to %20 |
| 2nd encoding (mistake) | hello%2520world | % gets re-encoded to %25, producing double encoding |
| Only 1 decode applied | hello%20world | The literal string %20 remains instead of a space |
2. Two different notations for a space: form data's '+' vs. API's '%20'
Separate from double encoding, there's another source of confusion: there are actually two different ways to represent a space in the first place. When an HTML form is submitted with method="GET", it follows the application/x-www-form-urlencoded spec and represents a space as a + sign. encodeURIComponent(), commonly used when building REST API requests, represents a space as %20 instead. Both notations are valid by spec, but a + outside a form-data context can be interpreted as a literal plus sign — so if you feed a query string built from a form directly into a REST API parser, that + can end up misread as an actual '+' character instead of a space.
3. How to prevent double encoding in practice
As this site's URL Encoder explains, encodeURIComponent is designed for encoding a "single" query parameter value, while encodeURI is designed for encoding an "entire," already-assembled URL. Double encoding usually happens when that distinction gets blurred — typically in defensive code that re-encodes a value "just in case" it wasn't already encoded. The most reliable safeguard is to first try decoding a value with decodeURIComponent before encoding it, to check whether it's already encoded. If a string shows %25 or a repeating %2X pattern, suspect double encoding and go back to check the original state.
4. Summary
- Double encoding happens when a % gets encoded again: %20 → %2520.
- Form's + and API's %20 are different notations: mixing up the contexts causes parsing errors.
- Check by decoding before encoding: getting in the habit of checking whether a value is already encoded is the most reliable prevention.
- encodeURIComponent for a single value, encodeURI for a whole URL: keeping the use cases separate avoids most double-encoding issues.
FAQ
Q. Why does %2520 happen?
A. It happens when a string that's already percent-encoded gets encoded a second time. A single space first becomes %20 during the first encoding pass. If that string is then encoded again without checking whether it's already encoded, the % character in %20 itself gets re-encoded to %25, leaving %2520 as the final result. If the server decodes this value only once, what's left isn't a space but the literal string '%20'.
Q. Do HTML forms and REST APIs encode spaces differently?
A. Yes. An HTML form with method=GET follows the application/x-www-form-urlencoded spec and encodes a space as +. A REST API request built with the commonly used encodeURIComponent(), on the other hand, encodes a space as %20. Both are valid representations, but depending on which spec the server parses against, a + may be misread as a literal '+' character instead of a space.
Q. Should I use encodeURIComponent or encodeURI to avoid double encoding?
A. Neither function prevents double encoding on its own. What matters is clearly distinguishing what you're encoding. Encode individual query parameter values with encodeURIComponent, and encode an already-assembled full URL with encodeURI — the key to avoiding double encoding is never re-encoding a value with another function after it's already been encoded.
Q. How can I tell if a value has been double encoded?
A. If a string contains %25 (the encoding of the percent sign itself) or a repeating %2X pattern like %2520, suspect double encoding. The most reliable check is to decode the input once (decodeURIComponent) before encoding it, to confirm whether it's already encoded, and apply encoding only to genuinely raw, unencoded text.
Q. Do URLs with Korean (or other non-Latin) characters also suffer from double encoding?
A. Yes. Encoding the Korean character '안' produces %EC%95%88. Encode that value again, and each % becomes %25, producing a much longer, messier string like %25EC%2595%2588. URLs with non-Latin characters actually make double encoding more noticeable, since the string grows dramatically longer — which makes it easier to catch.
Q. Can a URL typed directly into the browser's address bar also get double encoded?
A. The browser itself only encodes an address-bar input once before sending the request. Double encoding is mainly a problem that arises in application code, as a URL passes through multiple stages — frontend, backend, external API, and so on.
Q. What happens if I run decodeURIComponent on a string that's already decoded?
A. Applying decodeURIComponent to plain, unencoded text (with no percent signs) just returns the original string unchanged — no error. However, if there's a malformed sequence where % isn't followed by two valid hex digits, it can still throw a URIError.
Q. Why do & and = need to be encoded in a query string?
A. & and = are reserved characters that delimit parameters in a query string. If they appear literally inside a parameter value, the URL structure itself breaks, and the server misreads where one parameter ends and another begins — so they must always be encoded with encodeURIComponent.