Base64 Text Encoder / Decoder — UTF-8 safe, so Chinese and emoji survive
Encode text to Base64 or decode it back. Works on UTF-8 bytes, so Chinese and emoji do not throw the InvalidCharacterError you get from btoa. URL-safe output, optional = padding, byte counts and inflation ratio. Your input never leaves this device.
Worked encodings
| You type | As it comes out | URL-safe ticked | Padding dropped too |
|---|---|---|---|
Hi | SGk= | SGk= | SGk |
a?> | YT8+ | YT8- | YT8- |
hi?> | aGk/Pg== | aGk_Pg== | aGk_Pg |
🎉 | 8J+OiQ== | 8J-OiQ== | 8J-OiQ |
Encoding, not encryption
What Base64 does
Base64 reflows bytes as a bit stream: every 3 bytes (24 bits) are cut into four 6-bit groups, and each group indexes a fixed table of 64 characters — A-Z, a-z, 0-9, +, /. Six bits fit exactly in one printable ASCII character, which is the whole point: arbitrary bytes through a text-only pipe. A short final group is padded with = up to a multiple of 4: one leftover byte writes ==, two write =.
Encoding, not encryption
The table is public and fixed, so 5L2g5aW9 reverses to 你好 in one step with no key involved. Do not store passwords in it, and do not use it to “obscure” secrets — password: MTIzNDU2 in a config file is plaintext with extra steps. For real secrecy use encryption such as AES-GCM; Base64 is only the shell the ciphertext travels in.
The UTF-8 trap
btoa and atob are what everyone reaches for, but they speak Latin-1: every code point must be 0xFF or below. Chinese 你 is U+4F60, and an emoji is two surrogates, so btoa("你好") throws InvalidCharacterError. The fix is to take UTF-8 bytes out of the string first and do Base64 on those, which is what TextEncoder / TextDecoder plus a hand-written table do here. Decoding runs with fatal: true, so a pasted binary fails loudly instead of turning into a row of U+FFFD.
base64url, padding and size
/ separates path segments and + means a space in a query string, so JWTs, query parameters and filenames use the base64url alphabet from RFC 4648 §5: + → -, / → _, usually with = dropped too — it needs escaping in a query anyway, and the missing count follows from the length. Padding is on by default; untick the box for the JWT form. Decoding accepts either alphabet.
The cost is size. Three bytes become four characters, so output runs 33.3% longer, and one lonely byte costs two characters. A 100 KB image inlined as data:image/png;base64,… ships as 133 KB inside the HTML document, with no cache entry of its own.
Questions
Why does it decode lengths that are not a multiple of 4? Because padding is optional in the wild. Decoding follows the forgiving-base64 rule the HTML spec gives atob: whitespace is stripped, a count of 2 or 3 mod 4 is read as omitted padding and completed automatically — the note says how many = were implied. A count of 1 mod 4 is arithmetically impossible, so it errors instead of guessing.
The decoded text looks like garbage. Then the source probably was not UTF-8 — legacy GBK text, or genuinely binary. Both are reported rather than papered over with replacement characters; check the sender’s encoding first.
Where does my input go? Into the loop and back out again. The table is hard-coded in the browser-side script, there is no eval, and no call ever carries your text out, so what you paste stays on this device.