Base64 Encoder & Decoder
How it works
Why btoa() fails on Unicode
The browser built-in btoa() only accepts characters up to U+00FF, so btoa('安静 👋') throws InvalidCharacterError. Base64 encodes bytes, not characters — text has to become bytes first, and the encoding that makes the result portable is UTF-8. This tool encodes your text as UTF-8 bytes and Base64-encodes those, so emoji and CJK survive the round trip.
Base64 vs base64url
base64url is the variant used in JWTs and URLs: '+' becomes '-', '/' becomes '_', and the trailing '=' padding is dropped. That is why the segments of a JWT never end in '='. The decoder here accepts both alphabets and restores missing padding automatically.
Examples
| Clean everything · 安静 👋 | Q2xlYW4gZXZlcnl0aGluZyDCtyDlronpnZkg8J+Riw== |
| hello | aGVsbG8= |
| 👋 | 8J+Riw== |
FAQ
Why does btoa() throw InvalidCharacterError?
btoa() only accepts Latin-1 input (code points up to U+00FF). Any emoji, CJK or accented character outside that range makes it throw. Encode the string to UTF-8 bytes first, then Base64-encode the bytes — which is exactly what this tool does.
Why does my JWT Base64 have no = padding?
JWTs use base64url, which drops the '=' padding and swaps '+' and '/' for '-' and '_'. A strict standard-Base64 decoder rejects it. This decoder normalizes the alphabet and re-adds padding before decoding.
Is my text uploaded to a server?
No. The page ships a strict Content-Security-Policy: connect-src 'none' blocks fetch, XHR, WebSockets and beacons, while default-src 'self' and form-action 'none' block every other way markup could send data off-site. Encoding happens in your browser and cannot leave it.
Can I decode Base64 that contains line breaks?
Yes. Whitespace (including MIME-style 76-column line wrapping) is stripped before decoding.