Why Spaces Become %20 in URLs (And When They Become +)

If you’ve ever debugged a broken query string, there’s a good chance you’ve run into this confusing situation:

hello world

suddenly becomes:

hello%20world

But sometimes it becomes:

hello+world

And that’s usually the moment developers start wondering:

  • Which one is correct?
  • Why are there two different formats?
  • Does %20 mean the same thing as +?
  • Why does my backend decode one but not the other?
  • Why does Postman behave differently from the browser?

The confusing part is that both %20 and + can represent spaces — but they come from different encoding rules and are used in different contexts.

This article breaks down where %20 comes from, why forms use +, and how these differences create real production bugs in APIs, redirects, and backend systems.


Why Spaces Need Encoding in URLs

URLs cannot safely contain literal spaces.

This is because spaces are not valid characters inside standard URLs.

For example, this URL is technically invalid:

https://example.com/search?q=hello world

Browsers may try to fix it automatically, but servers, proxies, and APIs can interpret it inconsistently.

So spaces must be encoded into a safe format before transmission.


Why Spaces Become %20

The %20 format comes from standard URL percent-encoding.

Under the hood:

  • space = ASCII character 32
  • decimal 32 = hexadecimal 20

So a space becomes:

%20

The % symbol simply means:

the next two characters are a hexadecimal byte

Example:

hello world

becomes:

hello%20world

This is the standard encoding used in:

  • URLs
  • path segments
  • JavaScript encoding APIs
  • browsers
  • APIs
  • RFC 3986 compliant systems

Why Spaces Sometimes Become +

This is where things get confusing.

The + symbol does not come from standard URL encoding.

It comes from HTML form encoding:

application/x-www-form-urlencoded

which browsers use when submitting forms.


The Historical Reason

Early web forms needed a compact way to encode spaces.

Instead of using:

%20

they used:

+

This convention became widely adopted in form submissions and query strings.

So:

hello world

became:

hello+world

inside form-encoded data.


The Important Distinction

FormatContext
%20Standard URL encoding
+HTML form encoding

Most developers discover this difference accidentally during debugging.


Real Example: Browser Form Submission

Suppose you have a basic HTML form:

<form method="GET">
  <input name="q" value="hello world">
</form>

The browser usually sends:

?q=hello+world

—not:

?q=hello%20world

This surprises a lot of developers the first time they inspect raw network requests.


JavaScript Behaves Differently

Now compare that with JavaScript:

encodeURIComponent("hello world");

Output:

hello%20world

Notice:

  • JavaScript uses %20
  • browser forms often use +

Both can represent spaces, but not every backend handles them the same way.


The Bug That Happens in Production

This difference causes some genuinely annoying bugs.

Especially when:

  • frontend apps
  • backend APIs
  • proxies
  • older frameworks
  • third-party services

all interpret encoding slightly differently.


Example: Backend Stores Literal +

Imagine this request:

?q=hello+world

Some frameworks correctly decode it into:

hello world

Others don’t.

The result becomes:

hello+world

stored literally in the database.

This used to happen frequently in:

  • older Java stacks
  • legacy PHP systems
  • custom proxy layers
  • improperly configured API gateways

Why + Can Be Dangerous

Another subtle issue:

The + character itself is a valid character.

So what if your actual value contains a literal plus sign?

Example:

C++

If improperly encoded:

C++

may accidentally decode into:

C

or:

C

depending on how the server interprets +.


Correct Way to Encode Literal +

A literal plus sign should become:

%2B

Example:

encodeURIComponent("C++");

Output:

C%2B%2B

This preserves the actual plus symbols safely.


Why Browsers and APIs Behave Differently

One of the most frustrating parts of debugging encoding issues is that different systems follow different rules.

For example:

SystemSpace Encoding
encodeURIComponent()%20
HTML forms+
URLSearchParams+
Raw URL paths%20
Some backend frameworksboth
Some legacy systemsinconsistent

This inconsistency is why URL bugs often survive local testing but fail in staging or production.


URLSearchParams and the + Surprise

Modern JavaScript introduces another subtle twist.

Example:

const params = new URLSearchParams({
  q: "hello world"
});

console.log(params.toString());

Output:

q=hello+world

Not %20.

That’s because URLSearchParams follows form-urlencoded rules internally.

This surprises developers who expected consistency with encodeURIComponent().


Path Segments vs Query Parameters

Another important distinction:

Spaces in path segments should use:

%20

Example:

/products/hello%20world

Using + in paths is incorrect and can create routing problems.


Real Debugging Story: OAuth Redirect Failure

One of the nastiest bugs I encountered involved OAuth redirects.

The frontend encoded spaces using:

+

But the authentication provider expected:

%20

The redirect signature validation failed silently.

The error message?

invalid redirect_uri

No hint about encoding.

After tracing raw network logs for nearly an hour, the difference between + and %20 turned out to be the entire issue.

This kind of bug is surprisingly common in:

  • OAuth
  • SSO
  • payment gateways
  • signed URLs
  • CDN token systems

How Different Languages Handle Spaces

JavaScript

encodeURIComponent("hello world");

Output:

hello%20world

Python

from urllib.parse import quote

quote("hello world")

Output:

hello%20world

Python Form Encoding

from urllib.parse import urlencode

urlencode({"q": "hello world"})

Output:

q=hello+world

PHP

urlencode("hello world");

Output:

hello+world

While:

rawurlencode("hello world");

produces:

hello%20world

This difference alone has caused countless cross-language inconsistencies.


Best Practices for Handling Spaces in URLs

Use %20 for Standard URL Encoding

Especially in:

  • paths
  • APIs
  • redirects
  • encoded URLs

Expect + in Form Data

Especially from:

  • browser forms
  • URLSearchParams
  • older backend libraries

Always Encode Literal +

Use:

%2B

when the plus sign is actual data.


Avoid Manual String Concatenation

Prefer APIs like:

URLSearchParams

instead of manually building query strings.


Log Raw Requests During Debugging

This catches:

  • double encoding
  • malformed spaces
  • proxy transformations

much faster than inspecting parsed objects.


Related Resources

If you're debugging malformed query strings or API requests, these tools and guides are useful:


FAQ

Why does space become %20?

Because spaces are not valid in standard URLs, so they are percent-encoded using hexadecimal ASCII values.


Why do forms use + instead of %20?

HTML forms use:

application/x-www-form-urlencoded

which historically represents spaces using +.


Are %20 and + always interchangeable?

Not always.

Many systems treat them similarly in query strings, but not in:

  • path segments
  • signed URLs
  • OAuth flows
  • some backend frameworks

Should I use %20 or + in URLs?

For standard URL encoding, use %20.

+ is mainly associated with form encoding.


Why does URLSearchParams use +?

Because it follows form-urlencoded behavior internally.


How do I encode a literal plus sign?

Use:

%2B

Example:

encodeURIComponent("C++");

Output:

C%2B%2B

Final Thoughts

The %20 vs + distinction looks tiny on paper, but it creates a surprising number of real-world bugs.

Especially once:

  • browsers
  • APIs
  • proxies
  • OAuth providers
  • backend frameworks

all start interacting with each other.

The safest approach is understanding which encoding rules apply in which context instead of assuming all URL encoding behaves the same way.

And when debugging suspicious query strings or redirect URLs, it helps to inspect the raw encoded values directly instead of trusting browser output alone.

For quick testing and decoding during debugging sessions, this tool is genuinely handy:

URL Encoder/Decoder