I was debugging a payment webhook at 11pm and the payload had a receipt field that was 4,000 characters of base64. My first instinct was to paste it into a decoder and stare at the output. The output was a wall of garbage. That was my mistake — and it's the most common one I see colleagues make.
Base64 is not encryption. Decoding doesn't give you "the message" — it gives you bytes. Those bytes might be a PNG, a PDF, a UTF-8 string, or just noise, depending on what was encoded. When you decode base64 and see ‰PNG or %PDF at the start, that's the file header, and you've done it right.
In the terminal, decode with:
echo "SGVsbG8gd29ybGQ=" | base64 -d
# Hello world
In the browser, atob() gets you the same bytes — but for any data that isn't plain ASCII, pair it with a UTF-8 decoder so you don't get mojibake:
const bin = atob("5L2g5aW9");
const bytes = Uint8Array.from(bin, c => c.charCodeAt(0));
new TextDecoder().decode(bytes); // "ä½ å¥½"
If the original data was an image, the decoded bytes are a binary image file — of course they don't render as text. The fix isn't a different decoder; it's treating the output as a file. That's the whole point of the base64-to-image tool on this site: paste the string, get the actual picture, download it.
Three rules save me time:
1. Length. Valid base64 (with padding) is always a multiple of 4 characters. If it isn't, something got truncated.
2. Alphabet. Only A–Z a–z 0–9 + / = are legal. A stray - or _ means it's base64url, not plain base64.
3. Round-trip. Encode the decoded output again — if you don't recover the original string, the input was corrupt or the encoder was broken.
Decoding a data:image/png;base64,… URL from an API response is the most common real-world case I hit, and it's exactly what our decoder handles without you needing a command line.
The character set rules come from RFC 4648, which spells out why the alphabet is what it is.