Developer Guide

Percent-Encoding and Hex in URLs (%XX Explained)

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)

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:

encodeURI("https://example.com/a b");
// https://example.com/a%20b

encodeURIComponent("a=b&c=d");
// a%3Db%26c%3Dd

Debugging checklist

  1. Decide whether you are encoding a full URL or one parameter.
  2. Confirm UTF-8 (almost always).
  3. Do not double-encode (%2520 means someone encoded %20 again).
  4. When reading logs, replace % pairs with bytes, then decode UTF-8—or paste into a decoder.

How this relates to site tools

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?.