URL Encoding Explained: A Practical Guide to Percent-Encoding
July 22, 2026 · DevTools
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:
| Function | Encodes | Use for |
|---|---|---|
encodeURI() | Everything except URL syntax (:/?#&=+) | A complete, already-assembled URL |
encodeURIComponent() | Everything except unreserved chars | A 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:
| Character | Breaks | Encode as |
|---|---|---|
| space | Everything (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
- Find the parameter that's wrong on the server.
- Decode the incoming URL once — is the value correct now? If yes, the client under-encoded (or you decoded twice by accident elsewhere).
- Still wrong? Look for
%25— that's double-encoding; find the second encoder and remove it. - Value correct but type wrong (
+vs space)? You're mixing form convention with strict percent-encoding — standardize on%20and treat+as space only in query strings.
Common checklist
- Encode values, not whole URLs — keep syntax characters intact.
- Encode once — chase down
%25as the smell of double work. - In query strings, remember
+== space; use%2Bfor a literal plus. - Always UTF-8 before encoding non-ASCII text.
- 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
- Encoding binary data for transport? Read Base64 Encoding Explained
- Working with JWTs in URLs? The JWT Decoder handles Base64URL automatically