URL encoding (percent-encoding) is one of those foundational web concepts that causes real bugs when misunderstood. Here's how it works, when you need it, and the correct functions to use in different languages.
What is URL encoding?
A URL may only contain a restricted set of ASCII characters. Characters outside this set — including spaces, special symbols, accented letters, and non-ASCII characters — must be encoded as %XX where XX is the hexadecimal byte value.
Safe characters (don't need encoding): Letters (A–Z, a–z), digits (0–9), and: - _ . ~
Reserved characters (have special meaning in URLs): : / ? # [ ] @ ! $ & ' ( ) * + , ; =
Reserved characters must be encoded when they appear as data (not as structural URL characters). For example, a query string value containing & must be encoded as %26 — otherwise it breaks the URL structure.
Common encodings:
| Character | Encoded |
|-----------|---------|
| Space | %20 or + (in form data) |
| & | %26 |
| = | %3D |
| + | %2B |
| # | %23 |
| / | %2F |
| ? | %3F |
| @ | %40 |
| : | %3A |
For quick encoding and decoding: URL Encoder / Decoder handles all of this in the browser without code.
JavaScript: which function to use
encodeURI(url): Encodes a complete URL. Does NOT encode characters that are valid URL structure characters: : / ? # [ ] @ ! $ & ' ( ) * + , ; =
Use this when you have a full URL and want to make it safe without breaking its structure.
``javascript
encodeURI('https://example.com/search?q=hello world&lang=en')
// → "https://example.com/search?q=hello%20world&lang=en"
// Note: spaces encoded, but ? & = left as-is
`
encodeURIComponent(value): Encodes everything except letters, digits, and - _ . ! ~ * ' ( ). Encodes ALL special characters including /, ?, #, &, =.
Use this when encoding individual query parameter values or path segments.
`javascript
encodeURIComponent('hello world & more')
// → "hello%20world%20%26%20more"
// Note: & is now %26
encodeURIComponent('a=b&c=d')
// → "a%3Db%26c%3Dd"
`
Building a URL from parts correctly:
`javascript
const base = 'https://api.example.com/search';</p>
const params = {
q: 'hello world',
category: 'web & mobile',
page: 1
};
const queryString = Object.entries(params)
.map(([k, v]) => ${encodeURIComponent(k)}=${encodeURIComponent(v)})
.join('&');
const url = ${base}?${queryString};
// "https://api.example.com/search?q=hello%20world&category=web%20%26%20mobile&page=1"
`
Or use URLSearchParams:
<code>javascript
<p>const url = new URL('https://api.example.com/search');</p>
<p>url.searchParams.set('q', 'hello world');</p>
<p>url.searchParams.set('category', 'web & mobile');</p>
<p>console.log(url.toString());</p>
<p>// "https://api.example.com/search?q=hello+world&category=web+%26+mobile"</p>
</code>
Note: URLSearchParams uses + for spaces in the query string (application/x-www-form-urlencoded format), not %20. This is technically correct for query strings but may differ from what some servers expect.
Decoding:
`javascript
decodeURIComponent('hello%20world%20%26%20more')
// → "hello world & more"
decodeURI('https://example.com/search?q=hello%20world')
// → "https://example.com/search?q=hello world"
`
Python
`python
from urllib.parse import quote, unquote, urlencode, parse_qs
Encode a query parameter value
quote('hello world & more', safe='')
→ 'hello%20world%20%26%20more'
safe='' encodes everything including /
Encode a full URL (leave / and : intact)
quote('https://example.com/path?q=hello world', safe=':/?#[]@!$&\'()*+,;=')
→ 'https://example.com/path?q=hello%20world'
Encode multiple parameters as a query string
params = {'q': 'hello world', 'category': 'web & mobile', 'page': 1}
urlencode(params)
→ 'q=hello+world&category=web+%26+mobile&page=1'
Note: urlencode uses + for spaces (form data encoding)
Decode
unquote('hello%20world%20%26%20more')
→ 'hello world & more'
Parse a query string
parse_qs('q=hello+world&page=2')
→ {'q': ['hello world'], 'page': ['2']}
`
PHP
`php
// Encode individual values (for query strings and form data)
urlencode('hello world & more')
// → 'hello+world+%26+more'
// Encode individual values (raw percent-encoding, no + for spaces)
rawurlencode('hello world & more')
// → 'hello%20world%20%26%20more'
// Decode
urldecode('hello+world+%26+more')
// → 'hello world & more'
rawurldecode('hello%20world%20%26%20more')
// → 'hello world & more'
// Build a query string
$params = ['q' => 'hello world', 'page' => 1];
http_build_query($params)
// → 'q=hello+world&page=1'
`
Command line
`bash
Encode
python3 -c "from urllib.parse import quote; print(quote('hello world & more', safe=''))"
→ hello%20world%20%26%20more
Decode
python3 -c "from urllib.parse import unquote; print(unquote('hello%20world%20%26%20more'))"
→ hello world & more
With curl (automatically encodes for you)
curl "https://api.example.com/search?q=hello world" # curl handles encoding
Or explicitly:
curl --data-urlencode "q=hello world & more" https://api.example.com/search
`
Common mistakes
1. Double encoding
Encoding an already-encoded URL results in the % signs being encoded again:
- hello%20world
→ hello%2520world (wrong)
Only encode raw data, not already-encoded URLs.
2. Using encodeURI on a query parameter value
encodeURI doesn't encode & or = — it's designed for full URLs, not individual values. Using it on a query parameter value will corrupt the URL.
`javascript
// WRONG: & is not encoded
const url = https://api.example.com?q=${encodeURI('a=b&c=d')}<code>;</p>
// → "https://api.example.com?q=a=b&c=d" (breaks the URL)
// CORRECT
const url = https://api.example.com?q=${encodeURIComponent('a=b&c=d')};
// → "https://api.example.com?q=a%3Db%26c%3Dd"
`
3. Forgetting to decode server-side
Most web frameworks automatically decode query string parameters, but manual URL parsing may not. Always decode before using values.
4. The + vs %20 confusion
In application/x-www-form-urlencoded (HTML form data), spaces encode as +. In standard URLs, spaces encode as %20. Some APIs return errors if you mix these. Check the API docs — if they show + in examples, they expect form encoding.
URL encoding is simple in principle but has enough edge cases (double encoding, the + vs %20 distinction, encodeURI vs encodeURIComponent`) that it's worth knowing exactly which function to use in each context.
Originally published at https://snappytools.app/url-encoder-decoder/