Ever seen %20, %3A, or %2F in a URL and wondered what they mean? These are percent-encoded characters — the mechanism that allows URLs to carry special characters safely. This guide breaks down exactly how URL encoding works, from the byte-level mechanics to practical code examples in every major language.
Try our URL Encoder/Decoder tool to encode or decode any string instantly →
What Is URL Encoding (Percent Encoding)?
URL encoding, formally called percent encoding, is a mechanism defined in RFC 3986 for representing characters in a URI that are not allowed or have special meaning. It works by replacing each target character with a % sign followed by two uppercase hexadecimal digits representing the byte value.
URLs can only contain a limited set of characters from the ASCII character set. Characters are divided into reserved characters (which have structural meaning like /, ?, &) and unreserved characters (letters, digits, -, _, ., ~). Everything else must be percent-encoded before it can appear in a URL.
For example, a space character (ASCII 32, hex 0x20) becomes %20. A colon (ASCII 58, hex 0x3A) becomes %3A. This is why you see URLs like https://example.com/search?q=hello%20world — the space between "hello" and "world" has been percent-encoded.
// URL encoding in action
Original: https://example.com/search?q=hello world&lang=en
Encoded: https://example.com/search?q=hello%20world&lang=en
// Reserved characters and their encoded forms:
// space → %20 : → %3A / → %2F
// ? → %3F & → %26 = → %3D
// # → %23 + → %2B @ → %40URL Encoding Character Reference Table
Below is a quick-reference table of the most commonly percent-encoded characters. Bookmark this for daily use.
| Character | Encoded | Name / Purpose |
|---|---|---|
(space) | %20 | Space |
& | %26 | Ampersand / query separator |
? | %3F | Question mark / query start |
/ | %2F | Forward slash / path separator |
: | %3A | Colon / scheme separator |
= | %3D | Equals / key-value separator |
# | %23 | Hash / fragment identifier |
+ | %2B | Plus sign (space in forms) |
@ | %40 | At sign / userinfo separator |
% | %25 | Percent (the escape char itself) |
! | %21 | Exclamation mark |
" | %22 | Double quote |
< | %3C | Less than |
> | %3E | Greater than |
{ | %7B | Left curly brace |
} | %7D | Right curly brace |
[ | %5B | Left square bracket |
] | %5D | Right square bracket |
| | %7C | Pipe / vertical bar |
\ | %5C | Backslash |
How URL Encoding Works (Step by Step)
The encoding process follows three steps: (1) take the character, (2) convert it to its UTF-8 byte sequence, and (3) percent-encode each byte as %XX. For ASCII characters this produces a single %XX token. For multi-byte UTF-8 characters, each byte gets its own percent-encoded triplet.
Let's walk through several examples to see this in action:
// Step-by-step: How characters become percent-encoded
// Example 1: ASCII character (space)
// Character: " " (space)
// ASCII code: 32 → hex: 0x20
// Encoded: %20
// Example 2: ASCII character (ampersand)
// Character: "&"
// ASCII code: 38 → hex: 0x26
// Encoded: %26
// Example 3: Multi-byte UTF-8 (accented letter)
// Character: "é"
// Unicode: U+00E9
// UTF-8 bytes: 0xC3 0xA9 (2 bytes)
// Encoded: %C3%A9
// Example 4: Multi-byte UTF-8 (Chinese character)
// Character: "中"
// Unicode: U+4E2D
// UTF-8 bytes: 0xE4 0xB8 0xAD (3 bytes)
// Encoded: %E4%B8%AD
// Example 5: Multi-byte UTF-8 (emoji)
// Character: "🚀"
// Unicode: U+1F680
// UTF-8 bytes: 0xF0 0x9F 0x9A 0x80 (4 bytes)
// Encoded: %F0%9F%9A%80Modern programming languages handle this automatically — you rarely need to do the byte conversion yourself. Use the built-in URL encoding functions shown later in this guide, or try our online tool.
Encode & decode any string with our URL Encoder tool →
encodeURI vs encodeURIComponent in JavaScript
JavaScript provides two URL encoding functions, and choosing the wrong one is a common source of bugs. Understanding the difference is critical for building correct URLs.
encodeURI() encodes a full URI. It preserves characters that have structural meaning in URLs: : / ? # [ ] @ ! $ & ' ( ) * + , ; =. Use it when you have a complete URL and want to fix spaces or non-ASCII characters without breaking the URL structure.
// encodeURI() — preserves URL structure
const url = 'https://example.com/path name?q=hello world';
encodeURI(url);
// → "https://example.com/path%20name?q=hello%20world"
// ✓ :// / ? = are NOT encoded (structure preserved)encodeURIComponent() encodes a URI component such as a query parameter value. It encodes everything except letters, digits, and - _ . ~ ! ' ( ) *. This means it WILL encode & = + / : ? — exactly what you want when the value itself might contain those characters.
// encodeURIComponent() — encodes everything for use as a value
const value = 'price=100&discount=20%';
encodeURIComponent(value);
// → "price%3D100%26discount%3D20%25"
// ✓ = & % ARE encoded (they're data, not structure)
// Correct pattern: build URL piece by piece
const base = 'https://api.example.com/search';
const query = 'Tom & Jerry: The Movie';
const fullUrl = base + '?q=' + encodeURIComponent(query);
// → "https://api.example.com/search?q=Tom%20%26%20Jerry%3A%20The%20Movie"Rule of thumb: Always use encodeURIComponent() for query parameter values. Only use encodeURI() when encoding an entire URL where you need to preserve the structure.
// Comparison table — which characters get encoded?
//
// Character encodeURI() encodeURIComponent()
// ───────── ─────────── ────────────────────
// space %20 %20
// / / (kept) %2F (encoded)
// ? ? (kept) %3F (encoded)
// & & (kept) %26 (encoded)
// = = (kept) %3D (encoded)
// # # (kept) %23 (encoded)
// : : (kept) %3A (encoded)
// + + (kept) %2B (encoded)
// @ @ (kept) %40 (encoded)
// é %C3%A9 %C3%A9
// 中 %E4%B8%AD %E4%B8%ADURL Encoding in Different Programming Languages
Every major language has built-in URL encoding functions. Here are the most common patterns:
JavaScript
// JavaScript — encodeURIComponent / URLSearchParams
// Encode a query parameter value
encodeURIComponent('hello world&key=val');
// → "hello%20world%26key%3Dval"
// Decode
decodeURIComponent('hello%20world%26key%3Dval');
// → "hello world&key=val"
// URLSearchParams (auto-encodes, uses + for spaces)
const params = new URLSearchParams({ q: 'Tom & Jerry', page: '1' });
params.toString(); // → "q=Tom+%26+Jerry&page=1"Python
# Python 3 — urllib.parse
from urllib.parse import quote, quote_plus, unquote, urlencode
# Path encoding (spaces → %20)
quote('hello world/file') # → "hello%20world/file"
quote('hello world/file', safe='') # → "hello%20world%2Ffile"
# Query value encoding (spaces → +)
quote_plus('hello world&key=val') # → "hello+world%26key%3Dval"
# Build query string from dict
urlencode({'q': 'Tom & Jerry', 'page': '1'})
# → "q=Tom+%26+Jerry&page=1"
# Decode
unquote('%E4%B8%AD%E6%96%87') # → "中文"Java
// Java — URLEncoder (spaces → +)
import java.net.URLEncoder;
import java.net.URLDecoder;
import java.nio.charset.StandardCharsets;
String encoded = URLEncoder.encode(
"hello world&key=val", StandardCharsets.UTF_8
);
// → "hello+world%26key%3Dval"
String decoded = URLDecoder.decode(encoded, StandardCharsets.UTF_8);
// → "hello world&key=val"Go
// Go — net/url package
package main
import "net/url"
// Query value encoding (spaces → +)
url.QueryEscape("hello world&key=val")
// → "hello+world%26key%3Dval"
// Path segment encoding (spaces → %20)
url.PathEscape("hello world/file")
// → "hello%20world%2Ffile"
// Build URL safely with url.Values
vals := url.Values{}
vals.Set("q", "Tom & Jerry")
vals.Encode() // → "q=Tom+%26+Jerry"PHP
<?php
// PHP — urlencode (spaces → +) / rawurlencode (spaces → %20)
urlencode('hello world&key=val');
// → "hello+world%26key%3Dval"
rawurlencode('hello world&key=val');
// → "hello%20world%26key%3Dval"
http_build_query(['q' => 'Tom & Jerry', 'page' => 1]);
// → "q=Tom+%26+Jerry&page=1"
urldecode('hello%20world'); // → "hello world"
?>Ruby
# Ruby — CGI / URI
require 'cgi'
require 'uri'
CGI.escape('hello world&key=val')
# → "hello+world%26key%3Dval"
URI.encode_www_form('q' => 'Tom & Jerry', 'page' => '1')
# → "q=Tom+%26+Jerry&page=1"
CGI.unescape('hello%20world') # → "hello world"Common URL Encoding Mistakes
1. Double Encoding
Double encoding occurs when you encode a string that is already encoded. The % in %20 gets re-encoded to %25, producing %2520. The server then decodes it to %20 (a literal string) instead of a space. Always encode raw values exactly once.
// Double encoding — a very common bug
const value = 'hello world';
const encoded = encodeURIComponent(value); // "hello%20world"
// BUG: encoding again
encodeURIComponent(encoded);
// → "hello%2520world" (%20 → %25 + 20)
// Server decodes to "%20" (literal) instead of " " (space)
// FIX: encode raw values exactly once2. Encoding the Entire URL
Passing a full URL to encodeURIComponent() will encode the ://, /, ?, and = characters, completely breaking the URL. Only encode individual query parameter values or path segments that contain user data.
// WRONG: encoding entire URL
encodeURIComponent('https://example.com/api?q=test');
// → "https%3A%2F%2Fexample.com%2Fapi%3Fq%3Dtest" (broken!)
// CORRECT: encode only the parameter value
'https://example.com/api?q=' + encodeURIComponent('test value');
// → "https://example.com/api?q=test%20value"3. Space as + vs %20
In the application/x-www-form-urlencoded format (HTML forms), spaces are encoded as +. In RFC 3986 percent encoding, spaces are %20. Most servers handle both, but some strict APIs only accept one form. When building API requests, prefer %20.
// Space encoding: + vs %20
// HTML form submission (application/x-www-form-urlencoded):
// "hello world" → "hello+world"
// RFC 3986 (URI standard):
// "hello world" → "hello%20world"
// JavaScript functions:
encodeURIComponent('hello world'); // → "hello%20world" (RFC 3986)
new URLSearchParams({q: 'hello world'}).toString(); // → "q=hello+world" (form)4. Not Encoding Query Parameter Values
Forgetting to encode user-supplied values in query strings is a security risk (potential injection) and causes broken URLs. If a user searches for Tom & Jerry, the unencoded & will be interpreted as a parameter separator, splitting the value.
// Missing encoding breaks URLs
const search = 'Tom & Jerry';
// WRONG: unencoded & splits the value
'/search?q=' + search;
// → "/search?q=Tom & Jerry"
// Server sees: q="Tom " and Jerry="" (two params!)
// CORRECT: encode the value
'/search?q=' + encodeURIComponent(search);
// → "/search?q=Tom%20%26%20Jerry"
// Server sees: q="Tom & Jerry" (one param)Best Practices for URL Encoding
Always encode user input before inserting it into query strings or path segments. This prevents both broken URLs and potential injection attacks. Never trust that user-provided data is URL-safe.
Use library functions, not manual replacement. Functions like encodeURIComponent(), Python's urllib.parse.quote(), or Go's url.QueryEscape() handle UTF-8, edge cases, and the correct character sets. Rolling your own encoding logic is error-prone.
Test with special characters. Always test your URL-building code with inputs containing & = ? / # + % and non-ASCII characters like accented letters, CJK characters, and emoji. These are the inputs most likely to break.
Use URL-safe Base64 when embedding binary data. Standard Base64 uses + and / which conflict with URL syntax. URL-safe Base64 replaces them with - and _, avoiding the need for additional percent encoding.
// Best practice: use URL / URLSearchParams API
const url = new URL('https://api.example.com/search');
url.searchParams.set('q', 'Tom & Jerry: The Movie');
url.searchParams.set('lang', 'en');
url.toString();
// → "https://api.example.com/search?q=Tom+%26+Jerry%3A+The+Movie&lang=en"
// All encoding handled automatically!
// URL-safe Base64 (avoids + and / in URLs)
// Standard: "SGVsbG8gV29ybGQ=" (may contain + / =)
// URL-safe: "SGVsbG8gV29ybGQ" (uses - _ instead)Frequently Asked Questions
Why is space encoded as %20?
The space character has ASCII code 32, which is 0x20 in hexadecimal. Percent encoding represents a byte as % followed by its hex value, so space becomes %20. This is defined in RFC 3986.
What is the difference between + and %20 for spaces?
%20 comes from RFC 3986 (the URI standard) and is used in path segments and modern APIs. + comes from the application/x-www-form-urlencoded format used by HTML forms. In query strings most servers accept both, but %20 is more universally correct.
Do I need to encode the entire URL?
No. You should only encode individual components — query parameter values, path segments that contain user data, and fragment identifiers. The structural characters (://, /, ?, =, &) must remain unencoded so the URL can be parsed correctly.
What is the difference between encodeURI and encodeURIComponent?
encodeURI() encodes a complete URL, preserving structural characters like / ? & =. encodeURIComponent() encodes a single component (like a query value) and DOES encode those structural characters. For query parameter values, always use encodeURIComponent().
How do I decode a URL-encoded string?
In JavaScript use decodeURIComponent(). In Python use urllib.parse.unquote(). In Go use url.QueryUnescape(). In PHP use urldecode() or rawurldecode(). You can also use our online URL Decoder tool for quick decoding.
URL encoding is one of those fundamental web concepts that every developer needs to understand. Once you know how percent encoding works at the byte level, debugging broken URLs and building correct API requests becomes straightforward. Keep this guide bookmarked for quick reference.
Try our URL Encoder/Decoder for instant encoding and decoding →