English

Developer tools · URL encoder & decoder

Why + becomes a space when you decode a query string, and when it does not

· How it works

url-encoding javascript developer-workflow

A plus sign and its percent-encoded form remaining distinct through different decoders
Original ToolAcre vector illustration

Whether + means space depends on which decoder you call. This post explains how decodeURIComponent, URLSearchParams and server frameworks each treat +, and how to avoid turning a real plus into a space.

Why + becomes a space when you decode a query string, and when it does not

HTML form submissions use application/x-www-form-urlencoded format, where space becomes plus. A server receiving name=Alice+Smith replaces each plus with space before extracting the value. When a real plus belongs in data, like in a calculation 5+3, it arrives at the server as 5 3 after the form decoding step. This invisible conversion is the root of confusion.

JavaScript decoding produces different results depending on which function you use. URLSearchParams treats plus as space, matching server behavior. But decodeURIComponent leaves plus untouched, treating it literally. This asymmetry between functions is why the same input decodes differently. A developer expecting both decoders to produce the same result discovers they do not.

Two encodings that look alike — RFC 3986 percent-encoding versus application/x-www-form-urlencoded

Two encoding standards look similar but work differently. RFC 3986 defines percent-encoding: any character becomes %HH. Space becomes %20. The application/x-www-form-urlencoded standard adds a shorthand: space can be plus. Either works in form context, but plus is optional and specific to that standard. They are different domains with similar appearance.

Calling decodeURIComponent applies RFC 3986 decoding only. It reads %20 as space and plus as literal plus. URLSearchParams applies form decoding rules: percent-escapes become their characters, and plus becomes space. The two functions solve the same problem in different domains. Mixing them causes either a real plus to vanish, or a space to become plus and fail to convert.

decodeURIComponent leaves + alone; URLSearchParams turns it into a space — the two JavaScript behaviours compared

Server behavior varies, which compounds the problem. Rails or Django automatically apply the form rule: plus becomes space. But extracting and manually decoding the raw query string with a URL decoder leaves plus intact. The same value processed by different frameworks produces different results. Server code often handles this implicitly, hiding the issue until you write a custom decoder.

Example: a phone number field stores +1-555-0100 with plus as country code. An HTML form encodes it as %2B1-555-0100 because JavaScript encoded plus as %2B. The server receives this. If it applies form decoding, %2B becomes plus and the value is correct. If a proxy strips encoding, calling decodeURIComponent on the result produces +1-555-0100. Each layer decodes once.

What servers do — common framework behaviour in the query string and the request body, described in general terms

JavaScript can encode values using encodeURIComponent. Given a+b, it produces a%2Bb. When that encoded string reaches a server or form-aware decoder, %2B decodes as plus and the result is correct. If you encode using the form rule instead, a space becomes plus and a real plus becomes %2B. Either way, encoding produces a%2Bb. The interpretation depends on which decoding rule applies.

Test the round trip: start with a+b. Encode with encodeURIComponent to get a%2Bb. Decode a%2Bb with decodeURIComponent and recover a+b. Pass a+b to URLSearchParams: it treats plus as space, producing a b. Pass a%2Bb to URLSearchParams to get a+b back. The same input decoded two ways produces different outputs depending on which decoder you use.

Worked example: 'a+b' and 'a%2Bb' through both decoders — four outcomes in a table

Common mistakes follow directly. A developer decodes with decodeURIComponent and wonders why incoming form data with a real plus breaks. They should have used URLSearchParams. Conversely, someone uses URLSearchParams when they should use decodeURIComponent, and every literal plus disappears. Encoding twice produces %252B, requiring matching encoder-decoder pairs to decode correctly.

Another mistake is constructing a query string by hand as ?q=value without encoding. Any ampersand or equals in the value silently creates a new parameter. The browser does not second-guess concatenation; it treats the result as properly formed. Only intentional encoding with encodeURIComponent prevents this. URL encoder & decoder shows all three functions, revealing what each produces.

Common mistakes — decoding twice, or encoding a space as + in a path segment

The form encoding rule is called application/x-www-form-urlencoded because it describes the HTTP request body Content-Type header. HTML forms with no file uploads send the body in this format. Query strings in URLs use this convention too, though technically they have no official encoding standard. URL specs treat the query as opaque; plus meaning is not mandated. But in web applications, plus usually means space.

To guarantee correct behavior, encode deliberately and decode with the matching function. If you encoded with encodeURIComponent, decode with decodeURIComponent. If reading HTML form data or request bodies in form format, use URLSearchParams. Never guess based on appearance. A string like a+b is ambiguous. Decoders are not interchangeable.

What this does not cover — multipart form data and JSON request bodies

Multipart form data, JSON request bodies and other standards have separate encoding rules. JSON does not use plus for space or percent-encoding; it uses Unicode escapes. Multipart uses different boundaries. This article covers query strings and form-encoded bodies only, because that is where the plus ambiguity appears. Always check the Content-Type header and the RFC that defines it.

Always encode a literal plus as %2B when it belongs in a query value. URL encoder & decoder shows how plus is protected as %2B in component mode, separate from spaces which become %20. Pass a+b and a%2Bb through each mode, then examine the results. That comparison shows why the same input decodes differently. The difference is correct behavior of two different standards.

Takeaway: always encode a literal plus as %2B — how the URL encoder & decoder shows what a value looks like as a percent-encoded query value

Takeaway: plus in a query string is the form encoding shorthand for space, not a literal plus, unless it came from encoding that protected it as %2B. Wrong decoder loses that protection. URLSearchParams is safest in modern JavaScript; it handles form encoding and gives named parameter access. For raw strings, encodeURIComponent protects everything; decodeURIComponent interprets %20 and percents but treats plus literally.

Test this: build ?x=a+b by hand and paste into URL encoder & decoder. Inspect it and watch URLSearchParams split it into parameter x with value a b. Paste ?x=a%2Bb and see value a+b. Use encodeURIComponent to build the URL and compare. That visual confirmation clarifies the rule: form rules use plus, percent-encoding uses %20, mixing them is why plus vanishes into space.