Where your hex came from tells you which setting to pick
Hex arrives in a handful of shapes, and each one comes from a different place. Match the source to the row below and the decode works on the first try.
| Where you got the hex | What the paste looks like | Setting |
|---|---|---|
MySQL HEX() or a BLOB column | 546F6F6C657865 | Detect for me, UTF-8 |
| Wireshark, Follow TCP Stream, hex view | Offsets on the left, text on the right | Hexdump with offsets |
xxd or hexdump -C in a terminal | 00000000: 496e 766f ... | Hexdump with offsets |
| A C or Go byte array in source code | 0x49, 0x6e, 0x76 | Prefixed bytes |
A Python repr() or a shell string | \x49\x6e\x76 | Escapes |
| A URL query string | %49%6e%76 | Escapes, or the URL Decoder |
| A Windows registry export or a .NET dump | 49,6e,76,6f | Separated bytes, often UTF-16 LE |
Detect for me reads the paste and picks a shape, which is printed back to you in the Shape tag under the box. Override it when the guess looks wrong, which happens most often with a dump whose text column holds nothing but hex letters.
Paste a hexdump exactly as the terminal printed it
Most converters choke on dump output, because every line carries three parts and only the middle one is data. Press Load hexdump and the box fills with this:
00000000 49 6e 76 6f 69 63 65 20 32 30 32 36 2d 31 31 20 |Invoice 2026-11 |
00000010 66 72 6f 6d 20 54 6f 6f 6c 65 78 65 |from Toolexe|
0000001cThree things happen before the bytes get read. The offset at the start of each line goes, so 00000000 and 00000010 never reach the stream. The text column inside the pipe characters goes with it. The lone 0000001c on the last line, which is the file length rather than data, drops out as well.
What remains is 28 bytes decoding to Invoice 2026-11 from Toolexe. Paste the same dump into a converter without this step and the offsets turn into bytes, so the text opens with four null characters and a stray FF. Compare the Bytes count against the last offset in your dump to confirm nothing extra slipped in.
The xxd layout works the same way, with four hex digits per group instead of two and a colon after the offset. Both the GNU and the BSD spellings go through.
The same bytes read four ways
Hex has no encoding of its own. The bytes 4D C3 A1 6C 61 67 61 are seven numbers, and the encoding you choose decides which characters come out. Pick the wrong one and the result still looks like text, which is why a wrong answer here slips past review.
| Bytes | UTF-8 | Windows-1252 | UTF-16 LE | ASCII |
|---|---|---|---|---|
4D C3 A1 6C 61 67 61 | Málaga | Málaga | 썍没条� | M��laga |
C2 A3 32 39 2E 39 39 | £29.99 | £29.99 | ꏂ㤲㤮� | ��29.99 |
48 00 69 00 | H‹NUL›i‹NUL› | H‹NUL›i‹NUL› | Hi | H‹NUL›i‹NUL› |
54 6F 6F 6C 65 78 65 | Toolexe | Toolexe | 潔汯硥� | Toolexe |
The ‹NUL› marks in the third row are how a null byte looks with Name the control bytes ticked. Leave the box unticked and the same byte is invisible, which is the sneakier case.
Read the rows from the bottom up. Plain English inside the first 128 byte values decodes the same way under UTF-8, Windows-1252 and ASCII, so those three agree on Toolexe. The moment a byte crosses 7F, the three part company.
Two patterns are worth memorising, because they name the mistake out loud:
- A stray
Ã,Âorâbefore the character you expected. UTF-8 bytes read as Windows-1252. Switch to UTF-8. - A null byte between every letter. UTF-16 read one byte at a time. Switch to UTF-16 LE, or to UTF-16 BE when the nulls come first.
A leading FF FE or EF BB BF settles the question for you. Those are byte order marks, and the page names the one it finds in the message strip. FF FE 48 00 69 00 under UTF-16 LE gives Hi with the mark absorbed, while the same bytes under UTF-8 open with two replacement characters.
Question marks and black diamonds are a report, not a glitch
When a byte sequence breaks the rules of the encoding, the decoder substitutes U+FFFD, the replacement character. Browsers draw it as a black diamond with a question mark inside. Terminals often draw a plain question mark.
The Broken tag counts them. A count above zero means one of three things:
- Wrong encoding. Try Windows-1252 first, then UTF-16 LE. One of the two clears most cases.
- A truncated copy. A multi byte character cut in half leaves a lead byte with nothing after it. Check whether the last visible character in the source got clipped.
- Not text at all. A PNG, a ZIP or a compiled binary has no readable form. Bytes like
89 50 4E 47at offset zero are a PNG signature, and50 4B 03 04is a ZIP.
Opening the byte inspector marks the offending byte in red in the Hex column, so you get an offset to take back to the source rather than a vague failure.
An odd number of digits means something got cut
Every byte needs two hex digits. An odd total leaves one digit with no partner, so the page drops the final digit, says so in the message strip, and decodes the rest. Three causes account for nearly all of these:
- A copy from a terminal stopped mid byte because the selection ended inside a column.
- A leading zero went missing in a tool printing
afor0a. Hand written lists from a spreadsheet do this often. - The paste holds hex colour codes, not bytes.
#1F4ED8is six digits and three bytes, while#FFFis three digits of shorthand and belongs in the Hex to RGB Converter.
Tick Strict input to hold the output back until the stream is clean. Strict mode also reports any character outside 0-9 and A-F once separators are set aside, which is how a smart quote or a zero width space from a PDF gets caught.
Reading the byte inspector on the sample
Load sample fills the box with 43 bytes that decode to 40 characters. The gap between those two numbers is the point of the exercise, since two characters in the string need more than one byte each.
Open the inspector and look at these rows:
- Offset 0021 holds
C3. The Role column calls it the lead of a 2 byte character. On its own it is nothing readable. - Offset 0022 holds
A1, marked as a continuation byte. Together the pair givesá, soMálagacomes out whole. - Offsets 0028 to 002A hold
E2 9C 93, a 3 byte lead plus two continuations, which decode to the check mark.
The Bits column is there for the same reason. A UTF-8 continuation byte always starts 10, a 2 byte lead starts 110, and a 3 byte lead starts 1110. Once you have seen the pattern, a glance at the bits tells you where a character begins without counting.
Switch the encoding to ASCII while the sample is loaded and the Broken count jumps to 5, because the five bytes above 7F have no ASCII meaning. The Role column flags each one as outside ASCII.
Doing the same decode in code
Each one liner below has a trap worth knowing before you ship it.
Python
bytes.fromhex("4d c3 a1 6c 61 67 61").decode("utf-8")bytes.fromhex("4dc3a16c61 6761").decode("utf-8", errors="replace")bytes.fromhex accepts spaces between bytes from Python 3.7 onward, though not commas or 0x prefixes. Without errors="replace" a bad sequence raises UnicodeDecodeError and takes the whole batch down, so the argument belongs in any loop over user data.
JavaScript
const b = Uint8Array.from(hex.match(/[0-9a-f]{2}/gi).map(h => parseInt(h, 16)));new TextDecoder("utf-8").decode(b);Skip TextDecoder and reach for String.fromCharCode instead, and every byte above 7F turns into the wrong character, since fromCharCode treats a byte as a UTF-16 code unit. TextDecoder is the part doing the real work here.
PHP
echo hex2bin(preg_replace('/[^0-9a-f]/i', '', $hex));hex2bin throws a warning and returns false on an odd length string, so strip the separators first and check the length before the call.
Shell
echo "4d c3 a1 6c 61 67 61" | xxd -r -p
xxd -r hexdump.txt > original.binThe -p flag reads plain hex with no offsets. Drop -p and xxd -r expects a full dump with offsets instead, which is the inverse of the second sample on this page.
Going the other way? Text to Hex encodes a string into bytes, with a separator choice and the same encoding list.
Four jobs where hex to text is the wrong step
Hexadecimal shows up in places having nothing to do with character bytes. Sending those through a text decoder returns noise.
| What you have | Why a text decode fails | Use instead |
|---|---|---|
#1F4ED8 or rgba work | The three bytes are colour channels, not characters | Hex to RGB |
0x1A4 from a log or a register | One number written in base 16, not a byte stream | Hex to Decimal |
| A 64 character SHA-256 digest | A hash has no plain text behind it and never reverses | Nothing decodes a hash |
C0A80101 from a routing table | Four bytes standing for an address | Hex to IP |
Base64 is the other near neighbour. A string of mixed case letters ending in = is Base64 rather than hex, so send that to the Base64 Decoder. Hex uses twice the characters of Base64 for the same payload, which is the trade for staying readable in a terminal.
Known limits
- Text only. Decoding a PNG or a PDF gives you its bytes as characters, with no file on the other end. Keep binaries in a hex editor.
- Seven encodings. UTF-8, Windows-1252, ASCII, both UTF-16 orders, Windows-1251 and Shift JIS cover the hex people paste here. EBCDIC, Big5 and GB18030 are not on the list.
- No UTF-32. Four byte code units are rare outside memory dumps, so the list stops at UTF-16.
- The inspector stops at 1,024 rows. The text output covers the full paste, while the table shows the opening kilobyte. Longer streams belong in a hex editor with a search box.
- Large pastes wait for a pause. Past 40,000 characters the decode holds until you stop typing. A 1 MB paste finished in under 300 ms on a mid range laptop.
- Dump detection is a guess. A dump whose text column happens to read as hex, such as
cafe babe, confuses the sniffer. Set Input shape by hand when the Shape tag disagrees with what you pasted.
Everything runs in your browser. Your bytes never reach Toolexe, and a refresh empties the box.