How to URL Encode and Decode
A practical guide to percent-encoding — understand which characters need encoding, how the % notation works, and how to encode and decode URLs in JavaScript, Python, and the terminal.
What is URL encoding?
URLs can only contain a limited set of ASCII characters. Any character outside that safe set — spaces, Unicode, reserved delimiters like & and = — must be percent-encoded: replaced with a % followed by the character's two-digit hexadecimal UTF-8 byte value. This process is formally called percent-encoding and is defined in RFC 3986.
Which characters are safe?
Unreserved characters are always safe and never encoded: A–Z, a–z, 0–9, and the four symbols - _ . ~. Everything else is either a reserved character (like / ? # & =) that has structural meaning in a URL, or an unsafe character that must be encoded. A space encodes to %20 (or + in query strings under HTML form encoding).
Safe (unreserved): A-Z a-z 0-9 - _ . ~
Reserved (special): : / ? # [ ] @ ! $ & ' ( ) * + , ; =
Must encode: space → %20 < → %3C > → %3E " → %22 { → %7BencodeURI vs encodeURIComponent
JavaScript has two encoding functions with different scopes. encodeURI() encodes a full URL and leaves reserved characters alone (so / and ? stay intact). encodeURIComponent() encodes a single value — it encodes reserved characters too, making it the right choice for query parameter values.
const url = "https://example.com/search?q=hello world&lang=en";
// encodeURI — safe for a full URL
encodeURI(url);
// "https://example.com/search?q=hello%20world&lang=en"
// ↑ / ? & = are preserved — they are structural
// encodeURIComponent — safe for a query value
const query = "C++ & Java";
encodeURIComponent(query);
// "C%2B%2B%20%26%20Java"
// ↑ + and & are encoded — they would break the query string
// Decode
decodeURIComponent("C%2B%2B%20%26%20Java"); // "C++ & Java"Encoding in Python
Python's urllib.parse module provides quote() for encoding and unquote() for decoding. Use quote() for path segments and quote_plus() for HTML form-style query strings (which use + for spaces).
from urllib.parse import quote, unquote, urlencode
# Encode a path segment
quote("hello world/path") # "hello%20world/path" (/ is safe by default)
quote("hello world/path", safe="") # "hello%20world%2Fpath" (encode / too)
# Encode a query string
urlencode({"q": "C++ & Java", "page": 1})
# "q=C%2B%2B+%26+Java&page=1"
# Decode
unquote("hello%20world") # "hello world"Encoding from the command line
There is no built-in URL-encode command on most systems, but Python and curl make it easy.
# Encode using Python one-liner
python3 -c "from urllib.parse import quote; print(quote('hello world & more'))"
# hello%20world%20%26%20more
# Decode using Python one-liner
python3 -c "from urllib.parse import unquote; print(unquote('hello%20world'))"
# hello world
# curl --data-urlencode to send encoded form values
curl -G https://httpbin.org/get --data-urlencode "q=hello world"Common pitfalls
Double-encoding is the most frequent mistake: encoding an already-encoded string converts % to %25, turning %20 into %2520. Always decode before re-encoding. A second pitfall is using encodeURI() on query parameter values — it will not encode & and =, breaking the query string.
// Double-encoding bug
encodeURIComponent("%20"); // "%2520" ← wrong, was already encoded
// Fix: only encode raw values, never pre-encoded strings
const raw = "hello world";
encodeURIComponent(raw); // "%20" ← correctWhat is the difference between %20 and + in a URL?
%20 is the standard percent-encoding for a space (RFC 3986). The + sign represents a space only inside HTML form query strings (application/x-www-form-urlencoded), which is a different encoding scheme. In a URL path, a literal + means a plus sign, not a space.
Should I encode the entire URL or just the query parameters?
Encode individual values before assembling the URL. If you encode the full URL after construction, you will encode structural characters like / and ? that should stay literal.
Why does my encoded URL show %25 instead of %?
You are double-encoding. The string already contained a % character, and encoding it again turned % into %25. Decode first, then re-encode from the raw value.
How to Encode and Decode Base64
A practical guide to Base64 encoding and decoding — understand when and why to use it, how to handle Unicode, and how to do it in JavaScript, Python, and the command line.
Read guide →How HTML Escaping Works
A practical guide to HTML escaping — why it matters for security, which characters to escape, when to use it, and how XSS happens when you skip it.
Read guide →