People sometimes treat Base64 as a light form of encryption. It isn't one. Base64 represents arbitrary bytes using 64 printable ASCII characters (A-Z, a-z, 0-9, +, /), so you can embed binary data such as an image, a certificate, or a token inside something that only handles text: JSON, XML, an email body. Anyone can decode it in one line, so don't use it to hide anything sensitive.
Three bytes of input become four Base64 characters. That's why encoded output is always about 33% larger than the original.
Where you'll see it
- Small images embedded in CSS/HTML as
data:image/png;base64,... - JWT header and payload segments (see the JWT guide; that's Base64URL, not standard Base64)
- Basic Auth headers:
Authorization: Basic base64(username:password) - Binary attachments in JSON APIs that don't support raw bytes
The Unicode trap
This is the one that causes bugs. In a browser, btoa("café") throws InvalidCharacterError. btoa only understands Latin1 (0-255), and as a raw JS string character é falls outside that range. Encode to UTF-8 first:
btoa(unescape(encodeURIComponent("café")))
// or, cleaner, via TextEncoder:
const bytes = new TextEncoder().encode("café")
const base64 = btoa(String.fromCharCode(...bytes))This tool uses the TextEncoder version under the hood, so pasting emoji or accented characters won't blow up. If you're hand-rolling Base64 in your own code, expect to hit this bug first.
Standard vs URL-safe alphabet
Standard Base64 uses + and /, and both mean something in a URL. For a query string or a filename, use the URL-safe variant. It swaps + → - and / → _, and usually drops the trailing = padding. JWTs use URL-safe Base64 for this reason: the token has to survive being stuck in a header or a URL without extra escaping.