URLs cannot carry every byte as-is. Spaces, non-ASCII text, and reserved characters are escaped with a percent sign and two hex digits—percent-encoding (also called URL encoding). If you already work with hex dumps, this is the same digit alphabet with different framing: %20 instead of a bare 20.
Practice related conversions with Hex to String and String to Hex. For plain hex pitfalls, see odd length and whitespace.
The format
space → %20
# → %23
中 → %E4%B8%AD (UTF-8 bytes E4 B8 AD)
- Each escaped byte becomes
%+ two hex digits. - Unreserved characters (
A–Z,a–z,0–9,-._~) usually stay literal. - The hex part is case-insensitive; many servers normalize to uppercase.
Why developers confuse it with “string to hex”
String-to-hex tools emit a continuous digit string (48656c6c6f). Percent-encoding inserts % and only escapes what the URL grammar requires. Mixing the two produces broken query strings:
Wrong for a query value: name=48656c6c6f
Right for "Hello": name=Hello
Right for "a b": name=a%20b
UTF-8 is the default story on the modern web
Browsers encode non-ASCII path and query text as UTF-8 bytes, then percent-encode those bytes. That is why one Chinese character expands to three %XX groups.
console.log(encodeURIComponent("中")); // %E4%B8%AD
console.log(decodeURIComponent("%E4%B8%AD")); // 中
from urllib.parse import quote, unquote
print(quote("中")) # %E4%B8%AD
print(unquote("%E4%B8%AD")) # 中
encodeURI vs encodeURIComponent
In JavaScript, choosing the wrong helper is a classic bug:
encodeURIleaves:,/,?,#alone—meant for full URLs.encodeURIComponentescapes those too—meant for a single query value or path segment.
encodeURI("https://example.com/a b");
// https://example.com/a%20b
encodeURIComponent("a=b&c=d");
// a%3Db%26c%3Dd
Debugging checklist
- Decide whether you are encoding a full URL or one parameter.
- Confirm UTF-8 (almost always).
- Do not double-encode (
%2520means someone encoded%20again). - When reading logs, replace
%pairs with bytes, then decode UTF-8—or paste into a decoder.
How this relates to site tools
- Need raw byte hex for protocols or files? Use String to Hex.
- Need human text from hex dumps? Use Hex to String.
- Need colors or IPs? Those are different hex layouts—see hex colors and IP hex.
Worked example: building a safe query value
const q = {
city: "São Paulo",
ref: "a=b&c=d",
};
const query = Object.entries(q)
.map(([k, v]) => `${encodeURIComponent(k)}=${encodeURIComponent(v)}`)
.join("&");
// city=S%C3%A3o%20Paulo&ref=a%3Db%26c%3Dd
If you hex-encoded the whole JSON instead, intermediaries would not treat it as a query string—and operators could not read it in access logs without an extra decode step.
FAQ
Is + the same as %20?
In application/x-www-form-urlencoded bodies, + often means space. In path segments, prefer %20.
Can I percent-encode binary file bytes?
Yes, but Base64 or multipart upload is usually saner for large payloads. See Hex vs Base64.
Why does my server show mojibake after decoding?
Bytes were not UTF-8 (legacy GBK/Shift_JIS forms still appear). Decode with the charset that produced the bytes.
Overview: What is hexadecimal encoding?.