Why is my Base64 decode showing garbled text
Tools Nimbus is a free, no-signup developer toolkit that runs entirely in your browser, so your data is never uploaded to a server. Base64 that decodes to garbled text is almost never broken Base64: you decoded the bytes correctly but read them with the wrong character set, or fed URL-safe input to a standard decoder. Paste it into the Tools Nimbus Base64 Decoder, free with no signup, to round-trip UTF-8 cleanly.
Last updated June 2026
Decoding worked. Reading the bytes did not.
It helps to separate two steps that people lump together. Base64 is a way to represent arbitrary bytes as printable ASCII characters. Decoding reverses that and hands you the original bytes back. Those bytes are not necessarily text: they might be a PNG, a protobuf message, or UTF-8-encoded text. The garbling almost always happens in the second step, when something interprets the decoded bytes as text using the wrong character encoding. The Base64 layer did its job perfectly; the bug is downstream.
That distinction is the fastest way to debug. If you decode in the Base64 Decoderand the output is clean, but your code shows mojibake, the problem is your code's text handling, not the string. If even the tool shows garbage, the input itself is malformed and you have a different fix.
Symptom, cause, and fix at a glance
| Symptom | Most likely cause | Fix |
|---|---|---|
Accents/emoji become é, Â, � | UTF-8 bytes read as Latin-1 or Windows-1252 | Decode bytes, then read them as UTF-8 |
| Decoder throws or errors out | URL-safe - _ sent to a standard decoder | Replace - with +, _ with / |
| "Invalid length" or truncated tail | Missing = padding | Pad to a multiple of four characters |
| Random bytes / total nonsense | Input was never Base64, or is double-encoded | Verify it is Base64; decode twice if needed |
| Works partway, then breaks | Stray whitespace or newlines from copy-paste | Strip all whitespace before decoding |
Cause 1: the charset mismatch (the usual culprit)
This is the one that produces é where you expected é, or a black-diamond � replacement character. Your text was UTF-8 before it was Base64-encoded. A UTF-8 é is two bytes (0xC3 0xA9). When the decoder hands those two bytes to something that assumes Latin-1 or Windows-1252, each byte becomes its own character, so one letter turns into two. Emoji, which are four UTF-8 bytes, fan out into four mojibake characters or a box.
The fix is to never treat the decoded bytes as a Latin-1 string. In JavaScript, atob() gives you a binary string of raw bytes; wrap it in a TextDecoder to read it as UTF-8:
// atob alone misreads multi-byte UTF-8
const binary = atob(b64); // "é" style mojibake if printed
const bytes = Uint8Array.from(binary, c => c.charCodeAt(0));
const text = new TextDecoder("utf-8").decode(bytes); // correct "é"In Python the same trap is base64.b64decode(s).decode("latin-1") instead of .decode("utf-8"). The Base64 Decoder does the UTF-8 step for you, so if its output is clean you know the encoding is the only thing your code is getting wrong.
Cause 2: URL-safe input in a standard decoder
Standard Base64 uses + and / as its 63rd and 64th characters. Those characters are awkward in URLs and filenames, so URL-safe Base64 swaps them for - and _ and frequently strips the trailing = padding. JSON Web Tokens and most OAuth flows use this variant. Feed a URL-safe string to a strict standard decoder and it errors; feed it to a lenient one and you get garbage where the - and _ sat.
Convert before decoding: replace every - with + and every _ with /, then re-add = until the length is a multiple of four. If you are pulling apart a token, the JWT Decoder already knows the header and payload are URL-safe Base64 and handles the conversion for you. For the difference between the two alphabets in depth, see Base64 vs Base64URL.
Cause 3: missing or misplaced padding
Base64 encodes three bytes into four characters. When the input is not a multiple of three bytes, the encoder pads the output with one or two = signs so the length stays a multiple of four. Some systems drop that padding to save space. A decoder that expects it then reports an invalid length or silently truncates the last few bytes, which can chop the final character of your text. Re-add = until the string length divides evenly by four and the tail decodes correctly.
A subtler version is an = in the middle of the string. Padding is only ever valid at the very end, so a mid-string = means two separately encoded chunks were concatenated. Split them, or decode the parts independently.
Cause 4: it was never Base64, or it is double-encoded
If the output is complete nonsense rather than mojibake, question the input. A value that looks Base64-ish (letters, digits, the odd + or =) can actually be hex, a URL-encoded string, or a raw token in some other format. Decoding hex as Base64 produces random bytes. Conversely, if a clean decode hands you back something that itself looks like Base64, the value was encoded twice; decode it again. When the bytes are clearly not text at all (a PNG signature, for instance), the data is binary and was never meant to be displayed as a string.
Cause 5: whitespace and copy-paste artifacts
Copying a Base64 blob out of an email, a YAML file, or a chat window often introduces line breaks, tabs, or a non-breaking space. Many decoders tolerate leading and trailing whitespace but choke on whitespace in the middle, decoding everything up to the gap and then failing or producing a truncated result. Strip all whitespace first. If the value arrived inside a JSON payload, clean and inspect that payload in the JSON Formatter so you copy the exact string and nothing around it.
Decode it cleanly in one step
You do not have to guess which cause you are hitting. Open the Base64 Encoder and Decoder, paste your string, and read the output. It decodes to UTF-8 by default, so a correct round trip rules out the charset bug immediately; if the result is still wrong, work down the table above. Because it runs entirely in your browser using the native atob and TextDecoder APIs, the token, config value, or file you paste never leaves your device. If you are decoding the segments of a token, reach for the JWT Decoder instead, and browse the Tools Nimbus guides for more developer fixes.
How to prevent it
- Standardize on UTF-8 end to end. Encode text as UTF-8 bytes before Base64, and always decode the resulting bytes as UTF-8.
- Know which alphabet you are using. Tokens and URLs are almost always URL-safe (
-_); convert to standard before a standard decoder. - Preserve padding, or restore it to a multiple of four characters before decoding.
- Strip whitespace from copied blobs, and copy the exact value rather than a wrapped or quoted version of it.
- Remember Base64 is encoding, not encryption. It hides nothing, so decode freely, but never rely on it to protect a secret. See encoding vs encryption vs hashing for why.