What Base64 actually does
Base64 maps arbitrary binary data onto 64 printable ASCII characters, so that data can survive a channel built for text. It takes three bytes at a time — 24 bits — and re-slices them into four six-bit groups. Each six-bit group indexes a character from the alphabet A–Z a–z 0–9 + /, which has exactly 64 entries.
Output length = 4 × ceil(input bytes ÷ 3)
That ratio is the whole reason Base64 output is roughly 33% larger than the input. Four characters carry what three bytes carried. There is no compression and no cleverness — it is a fixed-width re-encoding, and the size penalty is the price of using only safe characters.
Padding, and why = appears
Input is processed three bytes at a time, but real data rarely divides evenly by three. When the final group is short, the encoder pads with zero bits and appends = characters to record how many bytes were actually present.
| Final group | Output characters | Padding | Example |
|---|---|---|---|
| 3 bytes | 4 | none | abc → YWJj |
| 2 bytes | 3 + 1 pad | = | ab → YWI= |
| 1 byte | 2 + 2 pad | == | a → YQ== |
Padding is optional in some contexts — JWTs strip it, and so do several URL-safe variants — because the length remainder is enough to reconstruct it. A Base64 string whose length leaves a remainder of 1 when divided by 4 is impossible and always indicates truncation. This decoder detects that case and tells you rather than returning garbage.
Standard versus URL-safe
The standard alphabet's last two characters, + and /, are both problematic in URLs: + is interpreted as a space in query strings, and / is a path separator. RFC 4648 defines a URL-safe variant that substitutes - and _ respectively.
| Index 62 | Index 63 | Variant | Used by |
|---|---|---|---|
| + | / | Standard (RFC 4648 §4) | Email, data URIs, HTTP Basic auth |
| - | _ | URL-safe (RFC 4648 §5) | JWTs, URL parameters, filenames |
The decoder above accepts both automatically — it normalises - and _ back before decoding, so you never have to know which variant you were handed.
A worked example
Encoding the three characters Man:
- ASCII bytes: M = 77, a = 97, n = 110
- As binary: 01001101 01100001 01101110
- Re-split into six-bit groups: 010011 010110 000101 101110
- As decimal: 19, 22, 5, 46
- Index the alphabet: T, W, F, u
- Result: TWFu
Three bytes in, four characters out, no padding — because 3 divides evenly by 3. Drop Man into the encoder above and you will get exactly that.
The Unicode problem
Most browser-based Base64 tools call JavaScript's built-in btoa() directly on the input string. That function only accepts characters in the range U+0000 to U+00FF — it throws an InvalidCharacterError on anything else, or silently mangles it depending on the path taken.
The correct approach converts the string to UTF-8 bytes first, then Base64-encodes those bytes:
const b64 = btoa(String.fromCharCode(...bytes));
This tool does that, which is why café and emoji round-trip cleanly. It also explains why the byte count for accented text exceeds the character count: é is two bytes in UTF-8, and most emoji are four.
Where Base64 is genuinely the right tool
- Data URIs. Embedding a small image or font directly in CSS or HTML, avoiding an extra HTTP request. Use the Copy as data URI button above.
- Email attachments. MIME requires it, and this is where the 76-character line wrapping convention comes from — older mail transfer agents could not handle long lines.
- HTTP Basic authentication. The Authorization header carries username:password Base64-encoded. Note that this is transport formatting, not security — it is why Basic auth over plain HTTP is unacceptable.
- JWT structure. The header and payload segments of a JSON Web Token are URL-safe Base64 with padding stripped. Decoding them is trivial; only the signature provides integrity.
- Binary in JSON. JSON has no binary type, so bytes travel as Base64 strings.
Frequently asked questions
Is Base64 secure?
Why is my encoded string bigger than the file?
My decoded output is full of question-mark diamonds
Can I decode a JWT here?
Does the order of my options matter?
Is my data uploaded when I select a file?
When Base64 is the wrong choice
The 33% size penalty is not free, and there are situations where it costs more than it saves.
| Situation | Problem | Better approach |
|---|---|---|
| Large images as data URIs | Cannot be cached separately, blocks HTML parsing, inflates every page load | Normal image request with cache headers; inline only under ~5 KB |
| Storing binary in a database | 33% more storage, plus encode/decode cost on every read and write | A native binary column type |
| Hiding a secret | Provides no protection at all | Encryption, or a secrets manager |
| Large file uploads | Memory pressure and needless CPU on both ends | Multipart form data or a direct binary body |
| Compressing data | Base64 makes data larger, never smaller | gzip or brotli — and compress before encoding, never after |
That last row matters more than it looks. Base64 output has high entropy relative to its alphabet and compresses poorly, so gzipping a Base64 string recovers far less than gzipping the original bytes and then encoding. The correct order is always compress, then encode.
Related variants worth knowing
Base64 is one of a family. Base32 uses a 32-character alphabet, is case-insensitive, and grows data by 60% — used where humans must transcribe strings by hand, such as TOTP authenticator seeds. Base58 drops the visually ambiguous characters (0, O, I, l) and is used in Bitcoin addresses for the same reason. Base16 is just hexadecimal, doubling the size but trivially readable, which is why hashes are printed that way.
The trade-off across all of them is identical: a smaller alphabet means safer, more transcribable output and more size overhead. Base64 sits at the point where the alphabet is still safe in most text channels while the penalty stays at a third.