Reconnecting… Connection lost. Reload Session expired. Reload

← Back to Blog

URL Encoding Explained: What %20 Means and How Percent-Encoding Works

Every developer has pasted a URL somewhere and watched it break because of a space or an ampersand. Here is exactly what is happening and how to handle it correctly in your code.

By Pankaj Kumar Β· 11 Jun 2026 Β· 5 min read
πŸ‘€
Pankaj Kumar β€” Senior Software Engineer
Built REST APIs in ASP.NET Core and Express where URL encoding edge cases were responsible for two separate production incidents. encodeURI vs encodeURIComponent is not a quiz question — it is a Friday afternoon outage.

Why URLs need encoding at all

A URL can only contain a specific subset of characters: letters, digits, and a handful of punctuation marks (- _ . ~). Everything else β€” spaces, non-ASCII text, and characters that have structural meaning in URLs β€” has to be encoded before it can safely appear in a link.

The reason is that characters like &, #, ?, and / mean specific things in URL syntax. An & separates query parameters. A # starts a fragment. A / separates path segments. If you want those characters to appear as data inside a URL rather than as URL structure, you have to encode them so parsers do not misread them.

How percent-encoding works

The encoding rule is straightforward: take the byte value of the character in UTF-8, write it in hexadecimal, and prefix it with a percent sign. A space is byte 0x20 in ASCII, so it becomes %20. A forward slash is 0x2F, so it becomes %2F. An ampersand is 0x26, so it becomes %26.

Non-ASCII characters get encoded per their UTF-8 byte sequence, which can be multiple bytes:

"cafΓ©"  β†’  "caf%C3%A9"
// Γ© = U+00E9, UTF-8 bytes: 0xC3 0xA9 β†’ %C3%A9

A few characters are always safe and never need encoding: A–Z, a–z, 0–9, and the four symbols - _ . ~. Everything else either needs encoding or has structural meaning that you may or may not want to preserve.

The encodeURI vs encodeURIComponent confusion

JavaScript has two encoding functions and developers mix them up constantly. The difference matters:

// encodeURI β€” treats the input as a complete URL
// leaves structural characters untouched
encodeURI("https://example.com/search?q=hello world&lang=en")
// β†’ "https://example.com/search?q=hello%20world&lang=en"
// The & is NOT encoded β€” it is still a query separator

// encodeURIComponent β€” treats the input as a single value
// encodes everything except letters, digits, and - _ . ~
encodeURIComponent("hello world&lang=en")
// β†’ "hello%20world%26lang%3Den"
// The & IS encoded β€” it is treated as data, not a separator

The practical rule: use encodeURIComponent for individual values you are inserting into a URL β€” query parameter values, path segments, anything user-supplied. Only use encodeURI if you have already assembled a complete URL and need to encode stray characters in it.

The most common mistake is using encodeURI for a query parameter value that contains an & or =. Those characters are not encoded by encodeURI, so your parameter bleeds into the URL structure and the server reads it as two separate parameters.

The plus sign β€” form encoding vs percent encoding

There is a second encoding scheme you will hit when dealing with HTML form submissions: application/x-www-form-urlencoded. In this scheme, spaces are encoded as + instead of %20. It is a historical quirk from early HTML that never went away.

// Percent-encoding (what encodeURIComponent produces)
"hello world"  β†’  "hello%20world"

// Form encoding (what HTML forms and URLSearchParams produce)
"hello world"  β†’  "hello+world"

Most web frameworks decode both automatically on the server side, so you often do not notice the difference. Where it matters: if you are manually constructing a request body or calling a third-party API that explicitly expects form encoding, make sure your spaces end up as + and not %20. And if a value contains a literal plus sign, it must be encoded as %2B regardless of which scheme you are using β€” otherwise the decoder reads it as a space.

Encoding path segments correctly

One place developers regularly get this wrong is dynamic path segments. If a filename or identifier can contain a slash, and you drop it directly into a URL path, you get path traversal behaviour β€” the slash is interpreted as a path separator, not data:

// If fileName is "reports/2026/january.pdf"
// this creates the URL /api/files/reports/2026/january.pdf
fetch(`/api/files/${fileName}`)   // wrong

// This creates /api/files/reports%2F2026%2Fjanuary.pdf β€” one segment
fetch(`/api/files/${encodeURIComponent(fileName)}`)   // correct

This is not just a correctness issue β€” it is a security issue. Unencoded slashes in user-controlled path segments are the foundation of path traversal attacks, where an input of ../../etc/passwd can navigate outside the intended directory on a poorly-validated server.

Double encoding β€” the subtle way things break

If a URL gets encoded twice, you end up with garbage. %20 encoded again produces %2520 (the % character itself becomes %25). When the server decodes %2520, it gets %20 β€” a literal percent-20 string, not a space. This produces 404s or query parameters with the wrong values.

It happens when a layer of your stack encodes a URL automatically and then your code encodes it again on top. Watch for it when using HTTP client libraries that automatically encode request URLs, working with redirect URLs, or passing URLs as query parameters inside other URLs.

Decoding in different languages

// JavaScript
decodeURIComponent("hello%20world")       // β†’ "hello world"

// C#
Uri.UnescapeDataString("hello%20world")   // β†’ "hello world"

// Python
from urllib.parse import unquote
unquote("hello%20world")                  // β†’ "hello world"

All of these handle standard percent-encoded sequences. If you are dealing with form-encoded data (the + for space variant), use the form-specific decoders: decodeURIComponent does not decode + as a space β€” you need URLSearchParams in the browser or urllib.parse.unquote_plus in Python.

Quick reference

  • Space β†’ %20 (percent-encoding) or + (form encoding)
  • / β†’ %2F, & β†’ %26, # β†’ %23, + β†’ %2B
  • Use encodeURIComponent for individual values, encodeURI for full URLs
  • Encode user-supplied path segments to prevent path traversal
  • Never encode a URL twice β€” % itself becomes %25
  • + is a space only in form-encoded data, not in percent-encoded URLs
Try the free tool
URL Encoder / Decoder

Encode or decode any URL or query string component instantly in your browser. Handles percent-encoding, plus-encoding, and full URL encoding in one tool.

Open URL Encoder / Decoder