URL Encoding Explained: What %20, +, and Percent-Encoding Mean
Open your browser's network panel and look at a real URL. You will see strings like Hello%20World, price%3D99, or tag=c%2B%2B. These are percent-encoded characters — a transformation applied because URLs can only safely contain a limited set of ASCII characters.
Why URL encoding exists
The URI specification (RFC 3986) defines a small set of unreserved characters that are always safe in a URL: A–Z a–z 0–9 - _ . ~. Everything else — spaces, slashes, equals signs, question marks, Unicode characters — must be percent-encoded before being placed in a URL component.
Percent-encoding works by replacing the character with % followed by its two-digit hexadecimal UTF-8 byte value. A space (byte 0x20) becomes %20. The / character (byte 0x2F) becomes %2F.
Reserved vs unreserved characters
| Category | Characters | Rule |
|---|---|---|
| Unreserved | A-Z a-z 0-9 - _ . ~ | Never encoded — always safe |
| Reserved (general delimiters) | : / ? # [ ] @ | Have structural meaning — encode when used as data |
| Reserved (sub-delimiters) | ! $ & ' ( ) * + , ; = | Have component-specific meaning — encode when used as data |
| Everything else | Spaces, Unicode, <>, % itself | Must always be encoded |
%20 vs + for spaces
You will see both %20 and + used for spaces, and they are not interchangeable:
%20is the correct percent-encoding of a space in any URL component (path, query, fragment)+means a space only inapplication/x-www-form-urlencodedbodies — the format used by HTML forms. It does NOT mean a space in a path segment.
If you are building a URL by hand, always use %20 for spaces. Use + only when serialising HTML form data.
Common percent-encoded characters
| Character | Encoded | Where it appears |
|---|---|---|
| Space | %20 | Search queries, file names with spaces |
/ | %2F | Slash inside a path segment (not as path separator) |
? | %3F | Literal question mark in query value |
= | %3D | Equals sign in a query value |
& | %26 | Ampersand in a query value |
+ | %2B | Literal plus in query value (to avoid space confusion) |
# | %23 | Hash in a query value (prevents fragment confusion) |
| € (U+20AC) | %E2%82%AC | Non-ASCII — UTF-8 bytes, each percent-encoded |
encodeURIComponent vs encodeURI
JavaScript has two built-in encoding functions. The key difference is what they leave alone:
encodeURIComponent(str)— encodes everything exceptA-Z a-z 0-9 - _ . ! ~ * ' ( ). Use this for individual query parameter values — it encodes/,?,&,=, and#.encodeURI(str)— leaves reserved characters alone (assumes the input is already a complete URL). Use this only if you have a full URL and want to encode stray non-ASCII characters in it without breaking the URL structure.
// Building a URL with query params — use encodeURIComponent
const q = encodeURIComponent("C++ programming & design");
const url = `https://example.com/search?q=${q}`;
// https://example.com/search?q=C%2B%2B%20programming%20%26%20design
// Using encodeURI on an existing URL — leaves / ? & = intact
encodeURI("https://example.com/path with spaces?q=hello");
// https://example.com/path%20with%20spaces?q=helloDecoding
The reverse functions are decodeURIComponent() and decodeURI(). Use decodeURIComponent() for individual query values. Calling decodeURIComponent() on a full URL will decode structural characters like %2F back to /, which may break path parsing.
Always decode query values on the server side — never in a browser URL bar. Most server frameworks (Express, FastAPI, Laravel) decode query parameters automatically.
Try it yourself
Use the free browser-based URL Encoder / Decoder on DevBench — no signup, runs entirely in your browser.
Open URL Encoder / Decoder