URL Encoding Special Characters: The Complete Guide (2026)
You paste a URL into your browser and it works. You add a space or an ampersand and suddenly the request fails, parameters get dropped, or you get a 400 error. URL encoding — also called percent-encoding — is the mechanism that makes arbitrary text safe to include in URLs. Understanding it precisely saves hours of debugging API calls, query strings, and form submissions. This guide explains every aspect of URL encoding with real examples and copy-paste code.
Why URLs Need Encoding
URLs can only contain a specific set of ASCII characters. The characters that are always safe in any URL component are the unreserved characters: uppercase and lowercase letters A–Z and a–z, digits 0–9, and four symbols: hyphen -, underscore _, period ., and tilde ~.
Every other character — spaces, ampersands, equals signs, slashes, question marks, hash symbols, non-ASCII characters, Unicode — must be encoded before it can safely appear in a URL. The encoding format is a percent sign followed by two hexadecimal digits representing the byte value: a space becomes %20, an ampersand becomes %26, a euro sign becomes %E2%82%AC (three bytes in UTF-8).
The Most Common Percent-Encoded Characters
| Character | Encoded | Where it appears |
|---|---|---|
| Space | %20 (or + in forms) | Search queries, names with spaces |
& | %26 | Parameter values containing & |
= | %3D | Parameter values containing = |
+ | %2B | Literal plus sign in query values |
/ | %2F | Slashes in path segments or values |
? | %3F | Question marks in parameter values |
# | %23 | Hash symbols in parameter values |
@ | %40 | At signs in URLs (emails in params) |
: | %3A | Colons in parameter values |
% | %25 | Literal percent signs |
" | %22 | Quote characters |
< | %3C | HTML angle brackets |
> | %3E | HTML angle brackets |
URL Components and What Needs Encoding
A URL has multiple components, and the encoding rules differ between them. The structure is: scheme://userinfo@host:port/path?query#fragment. Slashes in a path segment are significant — they separate path components. A slash that is part of a value in a path segment must be encoded as %2F. Slashes that are path separators must not be encoded.
Query string encoding
This is where percent-encoding matters most for everyday API work. Query string values must encode: spaces, ampersands, equals signs, plus signs, and all non-ASCII characters. Query string keys follow the same rules.
// WRONG — raw special characters break the query string https://api.example.com/search?q=hello world&filter=price>100 // RIGHT — values properly encoded https://api.example.com/search?q=hello%20world&filter=price%3E100
The + vs %20 distinction
In HTML form submissions (application/x-www-form-urlencoded), spaces are encoded as +, not %20. This is a legacy convention from early HTML. In modern URL encoding outside of form contexts, %20 is correct and unambiguous. The confusion: a literal plus sign must be encoded as %2B in form data. If your API is receiving + and treating it as a space when you intended a literal plus, this is why.
Rule of thumb: Use encodeURIComponent() in JavaScript (which produces %20 for spaces) for API calls. Use URLSearchParams for building query strings — it handles all encoding correctly.
Encoding in JavaScript
JavaScript has three encoding functions, each with a different scope. Choosing the wrong one is one of the most common JavaScript bugs.
// encodeURI() — encodes a complete URL, leaves structural characters alone
encodeURI('https://example.com/path with spaces?q=hello world');
// "https://example.com/path%20with%20spaces?q=hello%20world"
// Does NOT encode: : / ? # [ ] @ ! $ & ' ( ) * + , ; =
// encodeURIComponent() — encodes a single value/component
encodeURIComponent('hello & world = great!');
// "hello%20%26%20world%20%3D%20great!"
// Encodes everything except: A-Z a-z 0-9 - _ . ! ~ * ' ( )
// decodeURIComponent() — decodes a percent-encoded component
decodeURIComponent('hello%20%26%20world');
// "hello & world"
// URLSearchParams — the RIGHT way to build query strings
const params = new URLSearchParams({
q: 'hello world',
filter: 'price>100',
tag: 'C++ programming',
});
const url = `https://api.example.com/search?${params}`;
// "https://api.example.com/search?q=hello+world&filter=price%3E100&tag=C%2B%2B+programming"
// Fetching with encoded parameters — correct pattern
const url = new URL('https://api.example.com/search');
url.searchParams.set('q', userInput); // handles encoding automatically
url.searchParams.set('page', pageNumber);
const response = await fetch(url);
Common mistake: double encoding
// WRONG — encoding an already-encoded string
const encoded = encodeURIComponent('hello world'); // "hello%20world"
encodeURIComponent(encoded); // "hello%2520world" ← %25 = encoded %
// ALWAYS decode before re-encoding if the string might already be encoded
const safe = encodeURIComponent(decodeURIComponent(maybeEncoded));
Encoding in Python
from urllib.parse import quote, quote_plus, urlencode, urljoin
# quote() — encodes path segments (leaves / unencoded)
quote('/search/hello world')
# '/search/hello%20world'
# quote() with safe='' — encodes everything including /
quote('hello/world & more', safe='')
# 'hello%2Fworld%20%26%20more'
# quote_plus() — uses + for spaces (form encoding)
quote_plus('hello world & more')
# 'hello+world+%26+more'
# urlencode() — encodes a dict into query string
urlencode({'q': 'hello world', 'filter': 'price>100', 'tag': 'C++'})
# 'q=hello+world&filter=price%3E100&tag=C%2B%2B'
# Full URL construction
from urllib.parse import urlunparse, urlencode
query = urlencode({'q': 'search term', 'limit': 10})
url = urlunparse(('https', 'api.example.com', '/search', '', query, ''))
# "https://api.example.com/search?q=search+term&limit=10"
Encoding Non-ASCII and Unicode Characters
Non-ASCII characters (accented letters, Chinese characters, emoji) are first encoded to UTF-8 bytes, then each byte is percent-encoded. The euro sign € in UTF-8 is three bytes: 0xE2, 0x82, 0xAC — so it encodes as %E2%82%AC. The emoji 🔑 is four UTF-8 bytes: 0xF0, 0x9F, 0x94, 0x91 — encoding to %F0%9F%94%91.
encodeURIComponent('€'); // "%E2%82%AC"
encodeURIComponent('🔑'); // "%F0%9F%94%91"
encodeURIComponent('José'); // "Jos%C3%A9" (é = 0xC3 0xA9 in UTF-8)
Try encoding any text or URL with ToolPry's URL Encoder — it handles Unicode correctly and shows you both the encoded output and the individual byte values for non-ASCII characters.
Decoding Percent-Encoded URLs for Debugging
When you receive a URL in a log, an error message, or a webhook payload, it is often percent-encoded and difficult to read. Decoding it quickly reveals what the actual values are.
// JavaScript
decodeURIComponent('hello%20%26%20world%20%3D%20great');
// "hello & world = great"
// Python
from urllib.parse import unquote
unquote('hello%20%26%20world%20%3D%20great')
# "hello & world = great"
// Command line (Linux/Mac)
python3 -c "from urllib.parse import unquote; print(unquote('hello%20world'))"
Frequently Asked Questions
What is the difference between encodeURI and encodeURIComponent?
encodeURI is designed to encode a complete URL — it leaves characters that have structural meaning in URLs unencoded (: / ? # [ ] @ ! $ & ' ( ) * + , ; =). Use it when you have a complete URL that might contain spaces or non-ASCII but is otherwise valid. encodeURIComponent encodes everything except the unreserved characters — use it for individual parameter values, path segments, or any string that will be embedded inside a URL. In practice, you should almost always use encodeURIComponent for API work and use URLSearchParams or the URL constructor for building complete URLs.
Why do some URLs have %20 and others have + for spaces?
%20 is the RFC 3986 standard encoding for a space in a URL. + represents a space only in the application/x-www-form-urlencoded content type, which is what HTML forms submit by default. Both decode to a space in query string contexts, but they are not interchangeable everywhere. In path segments, + is a literal plus sign, not a space. When in doubt, use %20 — it is unambiguous in all URL contexts.
Do I need to encode forward slashes in path segments?
It depends on whether the slash is a path separator or part of a value. Path separators should not be encoded. A slash that is part of a value — for example, a filename containing a slash, or a base64url-encoded value — must be encoded as %2F to prevent it from being interpreted as a path separator. Many REST APIs use path parameters that might contain special characters; always encode parameter values with encodeURIComponent before interpolating them into URL paths.
How do I handle URL encoding in a REST API client?
Use your language's URL building library rather than string concatenation. In JavaScript, use the URL constructor with searchParams. In Python, use urllib.parse.urlencode and urllib.parse.urljoin. In Node.js with axios, pass parameters as the params option — it handles encoding automatically. Never build URLs by concatenating strings with user input; this causes both URL encoding bugs and potential security issues.