See the bytes behind text, in binary, hex, decimal or octal, and convert them back. Encoding is UTF-8 throughout, so accents take two bytes and emoji take four, the same as on the wire.
Hello world 01001000 01100101 01101100 01101100 01101111 00100000
01110111 01101111 01110010 01101100 01100100 Plain ASCII: the Latin alphabet, digits, common punctuation. These bytes match ASCII exactly, which is why any ASCII file is already valid UTF-8.
Accented Latin, Greek, Cyrillic, Hebrew, Arabic. Check byte limits, not character counts. é is one character but two bytes, so a 255-byte field holds only 127 of them.
Chinese, Japanese, Korean and most other scripts in common use, plus typographic characters such as the em dash, curly quotes and the euro sign.
Emoji, rare CJK characters, historic scripts. One visible emoji is often several code points: a flag is two regional indicators (8 bytes), and a skin-toned emoji is a base plus a modifier.
If a name shows up as é where it should read
é, the bytes are fine and something read them with
the wrong encoding. UTF-8 wrote two bytes, and a Latin-1 reader
printed those two bytes as two separate characters. Convert é here and
you see both bytes: C3 A9.
Use utf8mb4 for any new MySQL database.
MySQL's utf8 charset is a three-byte
subset of UTF-8 that cannot store emoji, so
inserting one throws an error or truncates the
value. The full encoding is called
utf8mb4.
const bytes = new TextEncoder().encode("café 👍");
console.log(bytes.length); // 10, not 6
console.log([...bytes].map((b) => b.toString(2).padStart(8, "0")).join(" "));
// Back again
console.log(new TextDecoder().decode(bytes));
// Three different lengths, all correct answers to different questions
const s = "café 👍";
console.log(s.length); // 8 UTF-16 code units
console.log([...s].length); // 6 code points
console.log(new TextEncoder().encode(s).length); // 10 UTF-8 bytes Which length you want depends on what is enforcing the limit: a database column counts bytes, a form validator usually counts code units, and a person counts what they can see.
s = "café 👍"
data = s.encode("utf-8")
print(len(s), len(data)) # 6 characters, 10 bytes
print(data) # b'caf\xc3\xa9 \xf0\x9f\x91\x8d'
print(data.hex(" ")) # 63 61 66 c3 a9 20 f0 9f 91 8d
print(" ".join(f"{b:08b}" for b in data))
print(data.decode("utf-8"))
# The mojibake, reproduced on purpose
print(data.decode("latin-1")) # café ð\x9f\x91\x8d <?php
$s = "café 👍";
echo strlen($s), PHP_EOL; // 10 bytes
echo mb_strlen($s, 'UTF-8'), PHP_EOL; // 6 characters
echo implode(' ', str_split(bin2hex($s), 2)), PHP_EOL;
// Binary
foreach (str_split($s) as $ch) {
echo str_pad(decbin(ord($ch)), 8, '0', STR_PAD_LEFT), ' ';
}
// strlen counts bytes. Using it for a length limit on user input
// silently rejects perfectly valid names. UTF-8 was designed so that ASCII is a subset: any 7-bit ASCII file is already valid UTF-8, byte for byte. That backwards compatibility is a large part of why it won over UTF-16 on the web, and it is why a system that assumes ASCII appears to work until someone types an accent. UTF-8 is also self-synchronizing: from any byte you can tell whether you are at the start of a character, so a decoder recovers after a corrupted byte instead of misreading the rest of the stream.
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.
UTF-8, which the web, JSON and most modern APIs use. Plain ASCII characters take one byte, an accented letter two, most CJK characters three, and an emoji four.
One visible emoji is often several code points. A flag is two regional indicators, and a skin-toned emoji is a base character plus a modifier, sometimes joined by a zero-width joiner. Each code point takes up to 4 bytes, so a family emoji can reach 25 bytes against a byte limit.
Yes, in both directions. Paste binary, hex or decimal and get the text back, with or without spaces between the groups.
This tool shows you the bytes. Base64 re-encodes those bytes with 64 safe ASCII characters so they can travel through a text-only channel. Use this tool to see what the bytes are, and Base64 to move them somewhere.
The database is storing Latin-1, UTF-16 or MySQL's utf8mb3 instead of full UTF-8. That mismatch is the usual cause of a name arriving as é instead of é.
For numbers rather than text, the number base converter handles binary and hex. To move bytes through a text-only channel, Base64 is what you want, and for URLs the URL encoder.