All posts

URL Encoding Explained: A Practical Guide to Percent-Encoding

July 22, 2026 · DevTools

url
encoding
percent-encoding
web
developer-tools

Paste hello world & goodbye into a query string and watch it break: the space ends the URL early in some contexts, and & silently splits your value into two parameters. The fix is percent-encoding (URL encoding) — the mechanism that lets arbitrary characters travel safely inside a URL.

You can test every example here with the URL Encoder/Decoder — it runs entirely in your browser.

How percent-encoding works

Every byte that isn't URL-safe is replaced by a % followed by its two-digit hexadecimal value:

space   → %20
&       → %26
=       → %3D
?       → %3F
#       → %23
ü       → %C3%BC   (UTF-8: two bytes, each percent-encoded)

So a search for c++ & c# becomes:

https://example.com/search?q=c%2B%2B%20%26%20c%23

Without encoding, + might be read as a space, & would split the query, and # would start a fragment the server never sees.

Reserved vs unreserved characters

RFC 3986 splits characters into two camps:

  • Unreserved (never need encoding): A–Z a–z 0–9 - _ . ~
  • Reserved (special meaning — encode when used as data, not as syntax): : / ? # [ ] @ ! $ & ' ( ) * + , ; =

Everything else — spaces, non-ASCII text, control bytes — must always be encoded. The key insight is that context matters: a / separating path segments is syntax and stays as-is; a / inside a value must become %2F.

A file named reports/2026 summary.pdf in a download link makes the difference concrete:

# The / in the filename must be encoded, the path separators must not
https://files.example.com/download/reports%2F2026%20summary.pdf

The query-string special case: + vs %20

Here's the trap. In the path, a space is always %20. But in query strings, the old application/x-www-form-urlencoded convention also allows + to mean a space:

?q=hello+world      → "hello world"   (+ = space, form convention)
?q=hello%20world    → "hello world"   (%20 = space, always safe)
?q=hello%2Bworld    → "hello+world"   (literal plus sign)

Decoders that don't know the form convention will turn + into a literal plus instead of a space — a subtle bug when you copy URLs between systems.

encodeURI vs encodeURIComponent

JavaScript gives you two functions, and picking the wrong one is a classic mistake:

FunctionEncodesUse for
encodeURI()Everything except URL syntax (:/?#&=+)A complete, already-assembled URL
encodeURIComponent()Everything except unreserved charsA single value going into a URL
const city = "São Paulo";

// ✅ Right: encode the value, then build the URL
`https://api.example.com/search?city=${encodeURIComponent(city)}`
// → ...?city=S%C3%A3o%20Paulo

// ❌ Wrong: encodeURI leaves & and = alone, so a value
// containing them would corrupt the query string
encodeURI(`https://api.example.com/search?city=${city}`)

Rule of thumb: building a URL from parts → encodeURIComponent each part. Encoding a finished URL for transport → encodeURI. Other languages have their equivalents: Python's urllib.parse.quote(s, safe='') behaves like encodeURIComponent, and Java's URLEncoder.encode(s, UTF_8) follows the form convention (spaces become +).

The double-encoding bug

If you've ever seen %2520 in the wild, you've met double-encoding: an already-encoded string got encoded again (%20 → the % becomes %25, giving %2520). It happens when encoding is applied at two layers — a frontend encodes a value, then a gateway encodes the whole request again. The symptom is a server receiving literal %20 instead of a space. The fix is always to encode once, at the boundary where the URL is assembled — and to decode only when consuming the value.

Non-ASCII text and UTF-8

URLs are bytes, so international text goes through UTF-8 first: each UTF-8 byte is then percent-encoded. 日本語 (9 bytes in UTF-8) becomes %E6%97%A5%E6%9C%AC%E8%AA%9E. Modern browsers hide this in the address bar, but it's what actually goes over the wire — and what your server receives.

Historically some systems encoded non-ASCII bytes in Latin-1 instead, which is why very old links sometimes decode to mojibake. If %FC decodes to ü in one system and garbage in another, you're seeing a UTF-8-vs-Latin-1 mismatch.

Form submissions: where it all started

The reason + means space at all is HTML forms. When a form posts with application/x-www-form-urlencoded (the default), every field is percent-encoded with the space-as-+ rule and joined with &:

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

<!-- body sent: q=hello+world -->

APIs that accept form-encoded bodies (OAuth2 token endpoints are everywhere) follow the same rule — so client_secret values containing + or & must be encoded, or authentication fails in confusing ways.

Quick reference: characters that break URLs

These are the offenders behind most production bugs:

CharacterBreaksEncode as
spaceEverything (ends URL in some parsers)%20 (or + in queries)
&Splits query parameters%26
=Ends a parameter name%3D
?Starts the query string early%3F
#Starts the fragment — rest never reaches the server%23
%Corrupts existing encoding%25
+Becomes a space in form decoding%2B
/Splits path segments%2F

That # row deserves a story: anything after a fragment is client-side only. A search query containing # (say, c# tutorial) that isn't encoded means the server receives a search for c and the browser keeps # tutorial to itself. It's one of the most common "the API ignored my parameter" mysteries.

URLs in emails, chats, and Markdown

Encoding matters even outside HTTP clients. A URL pasted into an email or chat app gets auto-linked by the recipient's parser, and many of them stop at spaces, <, >, or unbalanced parentheses — which is why Wikipedia links with parentheses (/wiki/Tree_(data_structure)) so often break in messages. The robust habit: when sharing a URL with exotic characters, percent-encode the path first, or wrap it in angle brackets in Markdown (<https://...>).

Debugging a broken URL: a 60-second procedure

  1. Find the parameter that's wrong on the server.
  2. Decode the incoming URL once — is the value correct now? If yes, the client under-encoded (or you decoded twice by accident elsewhere).
  3. Still wrong? Look for %25 — that's double-encoding; find the second encoder and remove it.
  4. Value correct but type wrong (+ vs space)? You're mixing form convention with strict percent-encoding — standardize on %20 and treat + as space only in query strings.

Common checklist

  1. Encode values, not whole URLs — keep syntax characters intact.
  2. Encode once — chase down %25 as the smell of double work.
  3. In query strings, remember + == space; use %2B for a literal plus.
  4. Always UTF-8 before encoding non-ASCII text.
  5. Decode at the last moment, where the value is actually used.

The URL Encoder/Decoder handles all of this for you — encode and decode with multiple modes, auto-detection, and instant analysis, all client-side so sensitive URLs (tokens, internal hostnames) never leave your machine.

Next steps

Tools mentioned in this post