URL encoding lab · Updated October 17, 2026

Encode Hex for a URL: Are You Encoding Bytes or Text?

If your input is a hex byte dump, parse each pair into a byte before percent-encoding it. Hex E4 B8 AD represents the UTF-8 bytes of 中, so the URL component is %E4%B8%AD. If your input is the literal text E4B8AD, keep it as E4B8AD: its letters and digits are already unreserved URL characters.

Choose the input type below. The tool keeps the byte representation separate from the decoded text so you can see where the two workflows diverge. It is for one component value, not for rewriting a complete URL.

Hex-to-URL byte lab

Hex mode accepts pairs of hex digits, optional spaces, and one optional leading 0x. Processing happens in your browser; this tool sends no input to a server.

UTF-8 preview:

Two inputs that look similar can mean different data

A hexadecimal digit is a way to write a number. Two digits can describe one byte, but a string containing those digits can also be ordinary text. Neither a URL encoder nor a server can infer which meaning you intended from the characters alone. Establish that meaning before converting anything.

Hex bytes E4 B8 AD become percent escapes E4 B8 AD with percent signs; the literal text E4B8AD stays E4B8AD. Two distinct inputs produce different URL components.
Choose bytes or text first. Percent signs describe the byte transport format, not the original input type.

For example, a device log might display a payload as C3 A9. That is two bytes and, if interpreted as UTF-8, one character: é. A product identifier printed as C3A9 could instead be four text characters. Sending the first interpretation when the service expects the second changes the identifier.

Our reproducible byte comparison

These examples were generated from fixed inputs, checked against Python's urllib.parse.quote_from_bytes(..., safe=""), and checked against the JavaScript converter used above. They are test cases, not claims about how a particular server handles binary input.

Input meaningActual hex bytesURL componentUTF-8 meaning
Hex bytes 4141AOne letter A
Text 4134 3141Two digits
Hex bytes C3 A9C3 A9%C3%A9é
Text C3A943 33 41 39C3A9Four characters
Text 中E4 B8 AD%E4%B8%ADOne character, three bytes
Hex bytes 00 FF00 FF%00%FFNot a valid UTF-8 text sequence
Text %4125 34 31%2541A literal percent sign and two digits
Hex bytes 2F 3F 262F 3F 26%2F%3F%26Slash, question mark, ampersand as data

Why 41 can become A instead of %41

The tool leaves ASCII letters, digits, hyphen, period, underscore, and tilde unchanged. These are the unreserved characters in RFC 3986. The byte 41 in hexadecimal is the letter A, so A is the readable component representation. An all-byte escape display could show %41, but adding percent signs to every byte is not required for an ordinary unreserved character. RFC 3986 §2.3

This rule does not mean every decoded punctuation character can be left in place. A slash or ampersand can act as structure in a URL. This lab treats those bytes as data inside a component, so it escapes them. The encoding-mode guide explains how to keep the surrounding URL separators intact.

Preserve bytes that are not valid UTF-8

A byte stream need not be text. The byte FF can be represented by %FF, but it is not a valid single UTF-8 character. The tool deliberately reports an invalid UTF-8 preview instead of silently replacing the byte with a replacement character. A server may reject, transform, or interpret that data according to its API contract. Percent-encoding alone does not make arbitrary bytes acceptable to a text endpoint.

For text mode, TextEncoder supplies UTF-8 bytes. For the preview, TextDecoder runs with fatal error handling; valid text is shown in quotes, including escapes for control characters. A leading UTF-8 BOM is kept in the preview rather than discarded. TextEncoder · TextDecoder fatal mode

Repeat the conversion in Python

from urllib.parse import quote_from_bytes

raw = bytes.fromhex("E4 B8 AD")
print(quote_from_bytes(raw, safe=""))  # %E4%B8%AD
print(raw.decode("utf-8"))            # 中

literal = "E4B8AD".encode("utf-8")
print(quote_from_bytes(literal, safe=""))  # E4B8AD

Do not call decode("utf-8") unless those bytes are supposed to be UTF-8 text. For bytes such as 00 FF, keep the raw byte representation and follow the receiving protocol. If the endpoint specifies Base64url instead, use its expected format; the Base64 tool covers that separate representation.

Questions to settle before copying the result

Should I URL-encode a hexadecimal ID?

If the ID is the literal text deadbeef, those eight characters are already unreserved. Parsing it into four bytes would change its meaning. Treat an ID as text unless the API explicitly defines it as a hex representation of bytes.

Can I just insert a percent sign before each pair?

Only when the input really represents bytes, has complete valid pairs, and the destination expects percent-encoded byte data. Never apply that shortcut to an arbitrary text identifier or a complete URL. The lab validates pairs and keeps unreserved ASCII readable.

Should I encode the generated % signs again?

Not at the same layer. Copy the generated component into the appropriate component position. Passing it through another value encoder changes % to %25. A builder that expects raw text should receive raw text instead; see the double-encoding guide for nested encoding layers.

Is this encryption?

No. Hex and percent-encoding are reversible representations. Neither conceals an identifier or provides access control.