Skip to content
encode2026-06-225 min read

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 handles it entirely in your 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:

  1. Take the character.
  2. Convert it to its UTF-8 byte representation.
  3. 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 in application/x-www-form-urlencoded data (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.


Need to quickly encode or decode a URL component? The URL Encoder/Decoder tool on DevToolkit Pro 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.


Advertisement