Hex to Text Converter

Paste a hex dump from a packet capture, a database column or a \x-escaped log line and read it as text. The bytes can be separated by spaces, colons or 0x prefixes. It also works the other way, and it reports bytes that are not valid UTF-8 instead of turning them into question marks without a word. Going the other way, the Separator menu writes the bytes as plain 4865, spaced or colon separated pairs, \x escapes or 0x prefixed values.

Hex
Text

1 Hex to Text Converter example

Hex, three different styles

48 65 6c 6c 6f 2c 20 5a 6f c3 ab 21 20 e2 98 95 0a
0x46 0x61 0x63 0x74 0x75 0x72 0x61 0x20 0x23 0x34 0x32 0x0a
\xf0\x9f\x98\x80

Decoded as UTF-8

Hello, Zoë! ☕
Factura #42
😀

The line breaks in the hex are only separators and are ignored, like the spaces. The line breaks in the output come from the 0a bytes at the end of each group, because a newline in text is one more byte.

2 Reading UTF-8 by eye

Bytes Text Why
48 65 6c 6c 6f Hello Plain ASCII, one byte per character
c3 ab ë Two bytes: 110xxxxx 10xxxxxx
e2 98 95 ☕ Three bytes, most of the BMP
f0 9f 98 80 😀 Four bytes, above U+FFFF
e9 é in Latin-1, invalid in UTF-8 A lone lead byte with nothing after it

The first byte tells you how long the character is: below 80 is a single ASCII byte, c2 to df starts a two-byte sequence, e0 to ef three, f0 to f4 four. Every continuation byte is between 80 and bf. A continuation byte where a lead byte should be, or a lead byte followed by ASCII, is what makes the decoder print U+FFFD.

3 When the output is garbage

If UTF-8 shows replacement characters, try Latin-1. The note under the tool counts the invalid bytes. A lone e9 is é in Latin-1 (and in Windows-1252, which is identical for letters) but invalid in UTF-8, so data from an old MySQL latin1 table or a Windows export usually decodes cleanly that way. If UTF-8 gives you é instead of é, the bytes were encoded twice: a program read UTF-8 as Latin-1 and saved it again.

Latin-1 here means ISO-8859-1: bytes 80 to 9F come out as C1 control characters, not the curly quotes and euro sign Windows-1252 puts there. If the hex is not text at all (an image header, a compressed blob), neither encoding will make it readable, and the byte count is the useful number.

How to use the Hex to Text Converter tool

  1. Choose Hex to text or Text to hex in the Direction dropdown.
  2. Paste hex in any common format (48 65, 0x48, \x48, 48:65) or type text. The result updates as you type.
  3. Pick UTF-8, Latin-1 or ASCII in Encoding. If UTF-8 shows replacement characters, the note bar says so; try Latin-1 next.
  4. For text to hex, choose a Separator and tick Uppercase if needed. Byte and character counts appear above the output.
  5. Upload File loads any file as hex. Swap reverses the direction, and Copy Result copies the output.
  6. For hex that goes into source code, pick \x (\x48\x65) for a string literal, 0x (0x48 0x65) or Comma (0x48, 0x65) for a byte array, and None (4865) for a bare digest-style string.

How to convert hex to text in JavaScript, PHP and Python

JavaScript / Node.js

// Hex -> text. Strip 0x and \x prefixes and any separators first.
const hex = "0x5a, 0x6f, 0xc3, 0xab 20 e2 98 95";
const clean = hex.replace(/0x|\\x|[^0-9a-f]/gi, "");
const bytes = Uint8Array.from(clean.match(/../g), (h) => parseInt(h, 16));
console.log(new TextDecoder().decode(bytes)); // Zoë ☕

// fatal: true throws on invalid UTF-8 instead of printing U+FFFD
try {
    new TextDecoder("utf-8", { fatal: true }).decode(new Uint8Array([0xe9]));
} catch (e) {
    console.log("not UTF-8, try latin1:", new TextDecoder("latin1").decode(new Uint8Array([0xe9])));
}

// Text -> hex, space separated
const out = Array.from(new TextEncoder().encode("Zoë ☕"),
    (b) => b.toString(16).padStart(2, "0")).join(" ");
console.log(out); // 5a 6f c3 ab 20 e2 98 95

// Node.js
console.log(Buffer.from("Zoë").toString("hex"));              // 5a6fc3ab
console.log(Buffer.from("5a6fc3ab", "hex").toString("utf8")); // Zoë

In browsers the label latin1 decodes as windows-1252, which only differs from ISO-8859-1 in bytes 80 to 9F.

PHP

<?php
echo bin2hex("Zoë ☕"), PHP_EOL;          // 5a6fc3ab20e29895
echo hex2bin("5a6fc3ab"), PHP_EOL;        // Zoë

// hex2bin() rejects separators and odd lengths, so clean up first
$hex = preg_replace('/0x|\\\\x|[^0-9a-f]/i', '', '0x5a, 0x6f, \xc3:ab');
echo hex2bin($hex), PHP_EOL;              // Zoë

// Spaced, uppercase output
echo strtoupper(implode(' ', str_split(bin2hex("Zoë"), 2))), PHP_EOL; // 5A 6F C3 AB

// Check the bytes are UTF-8 before trusting them
$bytes = hex2bin("e9");
var_dump(mb_check_encoding($bytes, 'UTF-8'));                  // bool(false)
echo mb_convert_encoding($bytes, 'UTF-8', 'ISO-8859-1'), PHP_EOL; // é

Python

# Text -> hex (bytes.hex with a separator needs Python 3.8+)
print("Zoë ☕".encode("utf-8").hex(" "))            # 5a 6f c3 ab 20 e2 98 95

# Hex -> text. fromhex() skips spaces but not 0x, colons or commas.
raw = "0x5a:0x6f:0xc3:0xab"
clean = raw.replace("0x", "").replace(":", "")
print(bytes.fromhex(clean).decode("utf-8"))         # Zoë

# Invalid UTF-8: choose between an exception, U+FFFD, or another codec
data = bytes.fromhex("e9")
print(data.decode("utf-8", errors="replace"))       # U+FFFD
print(data.decode("latin-1"))                       # é

Common errors and how to fix them

hex2bin(): Hexadecimal input string must have an even length
A digit is missing or a separator was left in. Strip spaces, colons and 0x prefixes first; if the length is still odd, the data was truncated.
ValueError: non-hexadecimal number found in fromhex() arg
bytes.fromhex() only skips whitespace. Remove 0x prefixes, colons and commas before calling it.
é appears where é should be
The text was UTF-8 encoded twice (read as Latin-1, then saved as UTF-8 again). Encode the broken text back to Latin-1 bytes and decode those as UTF-8 to recover it.
The output looks like random symbols
The bytes are probably not text at all: an image, a compressed blob or a hash. Hashes and keys are meant to stay in hex.

Good to know

Hex is a way of writing bytes, two digits per byte. What those bytes mean depends on the encoding, and UTF-8 (RFC 3629) is the one to assume unless you know otherwise. Its structure is visible in the hex: bytes below 80 are ASCII, C2 to F4 start a multi-byte character, and 80 to BF only ever continue one.

Specifications and references

This tool follows the specifications below. Read them when you need the exact normative wording, for example to settle an edge case between two implementations.

Frequently Asked Questions

What hex formats can I paste?

Plain pairs (48656c), space or colon separated bytes, 0x48 prefixes, \x48 escapes, comma-separated C arrays and %48 URL style, mixed with line breaks. Separators are ignored; only the hex digits count.

Why do I see � in the decoded text?

The bytes are not valid UTF-8, and each bad sequence is replaced with U+FFFD. The note under the tool counts them. Data from older systems is often Latin-1; switch the Encoding dropdown to try it.

How many bytes is my text?

The counter above the output shows characters and bytes. In UTF-8 an ASCII character is one byte, accented Latin letters two, most other scripts three, and emoji four.

Is this the same as converting a hex number to decimal?

No. This treats hex as a sequence of bytes that encode text. To convert a single number such as 0xFF to 255, use the number base converter.

Can I convert a file to hex?

Yes. Upload File reads the file in your browser and shows its bytes as hex, up to 5 MB. The file is not sent anywhere.

How do I convert text to \x hex escapes?

Set Direction to Text to hex and Separator to \x (\x48\x65). The output is one escape per byte with nothing between them, so Hello becomes \x48\x65\x6c\x6c\x6f, ready to paste inside a string literal in C, Python, PHP or a shell printf. Tick Uppercase if you want capital hex digits. Pasting such a line back in with Hex to text decodes it again.

How do I get hex with 0x prefixes or as a C byte array?

Choose 0x (0x48 0x65) to prefix every byte and separate them with spaces, or Comma (0x48, 0x65) to get a list you can paste straight into a C, Go or Java array initializer. Both are presentation only: the Hex to text direction strips the prefixes and commas again, so either form round-trips without editing.

How do I get the hex without spaces?

Pick None (4865) in Separator. That gives a continuous string of two digits per byte, the same form that Node's Buffer.toString("hex"), PHP's bin2hex() and Python's bytes.hex() produce, and the form most APIs expect for keys and digests. Colon (48:65) matches the style used for MAC addresses and OpenSSL certificate fingerprints.

More byte-level tools

Text to binary shows the same bytes in binary, decimal or octal. To turn bytes into something that survives a text channel use the Base64 encoder, and for single numbers rather than strings the number base converter is quicker.

More Encoders & Decoders

Browse all encoders & decoders

Copied to clipboard!