URL Encoding Explained: How to Handle Spaces, Chinese Characters & Special Symbols
URL encoding (formally called "percent-encoding") is one of those things developers encounter daily but rarely think about until something breaks. A space in a filename turns into %20, a Chinese character becomes a wall of percent signs, and somehow a + in a query parameter becomes a space on the server side. This article explains exactly how percent encoding works, why it exists, and how to handle the tricky cases without introducing bugs. For a quick encode or decode, an online url encoder offers Component / Full URI modes and an optional “+ as space” decode toggle — all in the browser.
How Percent Encoding Works
URLs can only contain a limited set of ASCII characters: letters, digits, and a handful of special characters (-, _, ., ~). Everything else — spaces, punctuation, non-Latin scripts, emoji — must be encoded before it can safely appear in a URL.
The mechanism is straightforward:
- Take the character.
- Convert it to its UTF-8 byte representation.
- Replace each byte with
%followed by its two-digit uppercase hexadecimal value.
For single-byte ASCII characters, this produces one percent-encoded triplet. For multi-byte characters (like Chinese, Japanese, or emoji), you get multiple triplets — one per byte.
Why Spaces Become %20 (or +)
The space character (ASCII 0x20) is the most commonly encoded character, and it has a confusing dual representation:
%20— The standard percent-encoding for a space, valid anywhere in a URL (path, query, fragment).+— A legacy encoding for spaces that is only valid inapplication/x-www-form-urlencodeddata (i.e., form submissions and query strings).
# In a URL path, space MUST be %20:
https://example.com/files/my%20document.pdf
# In a query string, both work (but mean different things in some parsers):
https://example.com/search?q=hello%20world # %20 = space
https://example.com/search?q=hello+world # + = space (form encoding)
The confusion arises because + is a literal plus sign in URL paths but means "space" in query strings. If you're building an API that accepts query parameters, you need to handle both — a url params parser can show you exactly how each parameter decodes. If you're generating URLs, pick one convention and stick with it.
Encoding Chinese Characters (and Other Multi-Byte UTF-8)
Here's where things get interesting. The character 中 (U+4E2D) is encoded in UTF-8 as three bytes: E4 B8 AD. So its percent-encoded form is:
中 → %E4%B8%AD
A full example:
Original: 你好世界
UTF-8: E4 BD A0 E5 A5 BD E4 B8 96 E7 95 8C
Encoded: %E4%BD%A0%E5%A5%BD%E4%B8%96%E7%95%8C
Each Chinese character produces 9 characters when percent-encoded (3 bytes x 3 chars per byte). This is why URLs with Chinese text get long quickly.
Here's how this works in JavaScript:
// encodeURIComponent handles UTF-8 percent-encoding
encodeURIComponent('中') // "%E4%B8%AD"
encodeURIComponent('你好') // "%E4%BD%A0%E5%A5%BD"
encodeURIComponent('hello 世界') // "hello%20%E4%B8%96%E7%95%8C"
// decodeURIComponent reverses it
decodeURIComponent('%E4%B8%AD') // "中"
Other Common Special Characters
| Character | Encoded | Context |
|---|---|---|
| Space | %20 or + | Paths use %20, forms use + |
& | %26 | Must encode in query values (separates params) |
= | %3D | Must encode in query values (separates key/value) |
# | %23 | Must encode (starts fragment identifier) |
? | %3F | Must encode in path (starts query string) |
/ | %2F | Must encode in path segments (separates segments) |
@ | %40 | Encode in path to avoid userinfo confusion |
€ | %E2%82%AC | 3-byte UTF-8 |
🎉 | %F0%9F%8E%89 | 4-byte UTF-8 (emoji) |
Common Mistakes and How to Avoid Them
Mistake 1: Double Encoding
The most common bug. You encode a value, then the framework encodes it again:
// BAD: double encoding
const param = encodeURIComponent('hello world'); // "hello%20world"
const url = `https://api.example.com/search?q=${encodeURIComponent(param)}`;
// Result: q=hello%2520world (% got encoded to %25)
// Server receives: "hello%20world" (literal string, not a space)
The fix: encode once, at the point where you assemble the URL. Most HTTP libraries (axios, fetch with URLSearchParams) handle encoding for you:
// GOOD: let URLSearchParams handle encoding
const params = new URLSearchParams({ q: 'hello world', name: '张三' });
const url = `https://api.example.com/search?${params.toString()}`;
// Result: q=hello+world&name=%E5%BC%A0%E4%B8%89
Mistake 2: Mixing + and %20
If your backend decodes + as space but your frontend sends %20 (or vice versa), you'll get mismatches:
// Server expects form encoding (+ for space)
// But client sends:
fetch('/api?q=hello%20world') // Some servers decode %20 correctly, others don't
// Safest: use URLSearchParams which produces consistent encoding
const params = new URLSearchParams();
params.set('q', 'hello world');
// Always produces: q=hello+world
Mistake 3: Encoding the Entire URL
Don't blindly encode a full URL — you'll encode the ://, /, ?, and & that are supposed to be structural:
// BAD: destroys URL structure
encodeURIComponent('https://example.com/path?q=hello world')
// "https%3A%2F%2Fexample.com%2Fpath%3Fq%3Dhello%20world"
// GOOD: encode only the dynamic parts
const baseUrl = 'https://example.com/path';
const query = encodeURIComponent('hello world');
const url = `${baseUrl}?q=${query}`;
Mistake 4: Assuming ASCII-Only
If your application handles internationalized input (usernames in Chinese, filenames in Arabic, search queries in Japanese), you must use UTF-8 percent-encoding. The old escape() function in JavaScript uses a non-standard encoding — never use it:
// NEVER use escape() — deprecated and non-standard
escape('中') // "%u4E2D" — this is NOT valid percent-encoding
// Always use encodeURIComponent()
encodeURIComponent('中') // "%E4%B8%AD" — correct UTF-8 percent-encoding
Practical Examples
Building a URL with Mixed Content
function buildSearchUrl(query, category, page) {
const params = new URLSearchParams({
q: query, // "北京 weather" → q=%E5%8C%97%E4%BA%AC+weather
category: category, // "food & drink" → category=food+%26+drink
page: page
});
return `https://example.com/search?${params.toString()}`;
}
buildSearchUrl('北京 weather', 'food & drink', 1);
// "https://example.com/search?q=%E5%8C%97%E4%BA%AC+weather&category=food+%26+drink&page=1"
Decoding URL Parameters Safely
function getUrlParams(url) {
const { searchParams } = new URL(url);
const params = {};
for (const [key, value] of searchParams) {
params[key] = value; // Already decoded by URL API
}
return params;
}
getUrlParams('https://example.com/search?q=%E4%BD%A0%E5%A5%BD&tag=a%2Bb');
// { q: "你好", tag: "a+b" }
// Note: %2B decodes to literal +, while + decodes to space
Handling Filenames with Special Characters
// Downloading a file with a Chinese name
const filename = '年度报告 2026.pdf';
const encodedFilename = encodeURIComponent(filename);
// "%E5%B9%B4%E5%BA%A6%E6%8A%A5%E5%91%8A%202026.pdf"
const downloadUrl = `/api/files/${encodedFilename}`;
// Server must decode the path segment to find the actual file
Quick Reference: What Needs Encoding Where
| URL Component | Characters That Need Encoding |
|---|---|
| Path segment | /, ?, #, [, ], @, !, $, &, ', (, ), *, +, ,, ;, =, space, non-ASCII |
| Query parameter value | &, =, +, #, space, non-ASCII |
| Fragment | Space, non-ASCII (browsers are lenient here) |
The simplest rule: if you're inserting a dynamic value into a URL, encode it with encodeURIComponent() (for path segments and query values) or use URLSearchParams (for query strings). Let the platform handle the details.
Double encoding: the bug that looks “almost right”
A value that already contains %20 gets passed through encodeURIComponent again and becomes %2520. Downstream systems that decode once still see a literal %20 string; systems that decode twice may “fix” it — until a third hop appears. Symptoms:
- Search boxes that show
%20or%2520in the UI instead of spaces - Logs where Chinese becomes
%25E4%25B8%25AD… - “Works in the browser address bar, fails in the API client” because one path decoded and the other did not
Discipline: encode at the boundary where a raw string becomes a URL component; decode at the boundary where a URL component becomes application data; never encode “just in case” in the middle. When debugging, paste the suspect string into the URL Encoder/Decoder and compare one-step vs round-trip output, then open the URL Params Parser on the full URL to see how the browser’s query parser interprets + and %20.
Path vs query: different reserved sets
encodeURI leaves ?, &, /, and # alone because it assumes you are encoding a whole URL. encodeURIComponent encodes those characters because it assumes you are encoding one component. Mixing them is a classic footgun: using encodeURI on a search keyword that contains & splits the query; using encodeURIComponent on a full URL turns https:// into an unusable blob. Prefer URL + URLSearchParams constructors when you can — they apply the right rules per slot.
FAQ
Is + always a space?
In application/x-www-form-urlencoded query strings, yes — + means space. In path segments, + is a literal plus unless your framework specially treats it. When you need a real + in a query value, emit %2B.
Should I encode the fragment (#…)?
Browsers are lenient with fragments, but proxies and analytics tools are not always. If the fragment carries structured data, percent-encode non-ASCII and reserved characters the same way you would for a query value.
Need to quickly encode or decode a URL component? The URL Encoder/Decoder tool on ToolVault handles percent-encoding entirely in your browser — paste your string, get the encoded result instantly, no data sent to any server. It handles multi-byte UTF-8 characters, + vs %20 differences, and full URL encoding so you can verify your output before shipping it.
Triage notes
The four common mistakes are ordered by real incident frequency: double encoding ranks first and is explicitly called the most common bug — you encode once, the framework encodes again, % becomes %25, and the server receives a literal %20 string. The dual representation of space gets its own section, with the confusion's root in one line: + is a literal plus in the path and a space in the query string, so pick one convention when generating URLs and keep it. The Chinese-encoding section does the math: three bytes per character and three encoded characters per byte, so one hanzi balloons into nine characters — which is why Chinese URLs get long so fast.
Related Tools
Related Articles
PDF to JPG: How to Pick the Right Resolution (DPI Guide + 3 Ways)
A complete guide to converting PDF pages to JPG: how DPI maps to sharpness, two-page and batch conversion, three approaches compared (in-browser local, command line, desktop software), plus a quality settings cheat sheet.
PDF Too Large for Email? How to Choose the Right Compression Quality (3 Local Levels)
How PDF compression works: lossy rasterization vs. lossless structure optimization, which of the three quality levels fits which use, and how to compare sizes before and after — entirely in your browser.
How to Split a PDF and Extract Pages: Range Syntax, Common Cases, and a Local Workflow
A complete guide to splitting PDFs by page range: the 1-3,5 range syntax, page-by-page extraction, and split-then-merge workflows — all in your browser, nothing uploaded.