Mojibake: ðŸ / é / ’ instead of real text (UTF-8 vs Windows-1252)
The bytes are fine. Something read them with the wrong codebook, so every multi-byte character exploded into two or three "Latin" letters.
What it looks like
| Intended | Mojibake you see | What happened |
|---|---|---|
é (U+00E9) | é | UTF-8 C3 A9 read as Windows-1252 |
' curly apostrophe | ’ | UTF-8 E2 80 99 read as CP1252 |
😀 | 😀 | 4-byte UTF-8 emoji read as CP1252 |
é again | é / é twice | Double-encoded: UTF-8 of already-mojibaked text |
Kid-friendly version of the same bug: Wirebook emoji lesson (scene B).
How to spot it in 30 seconds
- Accented European text suddenly has
Ã,Â,âeverywhere. - Emoji become a short run of symbols starting with
ðŸorâ. - The string length in "characters" is longer than the intended text (each UTF-8 byte became its own character).
- Paste into What is this? or compare before/after in the text diff. Encoding as UTF-8 with the Base64 tool shows the real bytes.
Undo a single round of mojibake
If UTF-8 was read as Latin-1/CP1252, each resulting character's code point is the original byte. Re-encode those code points as Latin-1 bytes, then decode as UTF-8:
# Python
fixed = bad.encode("latin-1").decode("utf-8")
# Node (Buffer)
fixed = Buffer.from(bad, "latin1").toString("utf8")
# MySQL (one-shot repair of a column that stored mojibake as characters)
UPDATE t SET col = CONVERT(CAST(CONVERT(col USING latin1) AS BINARY) USING utf8mb4);
Warning: only do this when you have confirmed a single mix-up. Double-encoded text needs the trick applied twice, and applying it to already-correct UTF-8 will crash or make things worse. Keep a backup. For emoji that became ? in the database, the data is gone — that is the utf8 vs utf8mb4 problem, not mojibake: Incorrect string value / utf8mb4.
Fix it at the source
| Layer | Set UTF-8 explicitly |
|---|---|
| HTML | <meta charset="utf-8"> as the first child of <head>; serve Content-Type: text/html; charset=utf-8 |
| Editor / files | Save as UTF-8 (preferably without BOM). In VS Code the status bar shows the encoding. |
| Browser form / fetch | Default is UTF-8 for modern HTML. Avoid legacy accept-charset traps. |
| Python | open(path, encoding="utf-8"); set PYTHONUTF8=1 on Windows; never rely on the system default for text. |
| Node | Buffers and fs.readFile(…, "utf8"). Be careful with latin1 / binary aliases — they are 1:1 byte maps, useful for repair, dangerous as a default. |
| MySQL / MariaDB | Connection + table + column all utf8mb4. SET NAMES utf8mb4. See the utf8mb4 guide. |
| HTTP APIs | JSON is UTF-8 by definition. For form bodies, declare charset. Do not "helpfully" re-encode already-decoded strings. |
Double encoding
A common pipeline bug: take a correct Unicode string, encode to UTF-8 bytes, then treat those bytes as a Latin-1 string and encode to UTF-8 again. You get é-style mess. Decode once to get back to single mojibake, then once more to recover the original — or better, find the extra encode and delete it.
Related
Emoji shows as □ / tofu · MySQL Incorrect string value / utf8mb4 · Invisible zero-width characters · Base64 is not encryption (emoji must be UTF-8 bytes first) · Unexpected token in JSON · Wirebook emoji lesson · Anvil text X-ray API · UnicodeDecodeError utf-8
Questions
What is mojibake?
Text that looks like gibberish because bytes were decoded with the wrong character encoding. UTF-8 read as Windows-1252 is the most common web case.
Why do I see é instead of é?
The UTF-8 bytes for é are C3 A9. Read as Windows-1252 they become à (C3) and © (A9). Re-decode correctly as UTF-8 to fix.
Why does my emoji look like 😀?
Same bug: a 4-byte UTF-8 emoji was decoded as single-byte Windows-1252/Latin-1. The bytes are usually still recoverable.
How do I fix mojibake in Python?
If UTF-8 was misread as Latin-1: bad.encode('latin-1').decode('utf-8'). Confirm on a sample first; double-encoded text needs two passes.
Is � mojibake?
No. � (U+FFFD) means the decoder already gave up on invalid bytes. Mojibake is wrong but still reversible characters like é or ðŸ.
Published .