Base64 shows up everywhere — data URIs, JWTs, email attachments, Basic Auth headers, API payloads. And it's misunderstood almost as often as it's used. Plenty of developers reach for it thinking it's some kind of security measure. It isn't. It's an encoding, not encryption. Let's clear that up and cover when it's genuinely useful.
What Base64 actually is
Base64 is a way of representing arbitrary binary data using only 64 printable ASCII characters: A-Z, a-z, 0-9, plus + and /, with = used for padding. The point is to move binary safely through systems that were designed for text.
The mechanism is simple. It takes three bytes (24 bits) at a time and splits them into four 6-bit groups. Each 6-bit group maps to one of those 64 characters. Three bytes in, four characters out — which is why Base64 output is always about 33% larger than the input.
Why the size increase matters
That overhead is the price you pay. If you Base64-encode a 3 MB image into a data URI, you're now shipping roughly 4 MB of text. For small payloads it's negligible. For large ones, it's a real cost you should weigh before committing.
When you'd actually use it
Base64 earns its place whenever binary data has to travel through a text-only channel. Some common cases:
- Data URIs — embedding small images, fonts, or SVGs directly in HTML or CSS to save a request.
- JSON and XML payloads — JSON has no native binary type, so a file uploaded in a JSON body is usually Base64-encoded.
- Email attachments — MIME uses Base64 because SMTP was built around 7-bit ASCII.
- HTTP Basic Auth — the
Authorizationheader sendsbase64(username:password). - JWTs — each segment is Base64url-encoded (more on that variant below).
- Storing binary in text config or env vars — like an encoded TLS cert dropped into a CI secret.
Notice the pattern: every one of these is a transport problem, not a confidentiality problem.
The security misconception
This is the big one. Base64 is completely reversible with no key. Anyone can decode it in a second. If you Base64-encode a password thinking you've hidden it, you've hidden nothing — you've just made it slightly less readable to a human skimming logs. Treat Base64 output as plain text for all security purposes. If you need secrecy, encrypt. If you need integrity, sign or hash.
Common gotchas
Standard vs URL-safe
Standard Base64 uses + and /. Both are problematic in URLs and filenames — / is a path separator and + can be interpreted as a space. The URL-safe variant (Base64url) swaps them for - and _ and often drops padding. JWTs use Base64url. If you decode a token with a standard decoder and get garbage, this mismatch is usually why.
Padding
The = characters at the end pad the output to a multiple of four. Some encoders omit padding, some strict decoders demand it. If a decode fails on otherwise valid-looking input, try adding or stripping =.
Character encoding of the source
Base64 encodes bytes, not characters. Before you encode a string, you have to decide what bytes it becomes. Encode "café" as UTF-8 and you get different bytes than Latin-1, so you get different Base64 output. Always be explicit about the source encoding, especially across languages. This bites teams where a Java service encodes and a Node service decodes with mismatched assumptions.
Line breaks
MIME-style Base64 inserts a newline every 76 characters. Some parsers choke on those newlines. If you're pasting encoded data and getting invalid-input errors, check for stray whitespace.
A short worked example
Let's encode the string Hi! by hand to see the mechanics. Its ASCII bytes are:
H = 72 = 01001000
i = 105 = 01101001
! = 33 = 00100001Concatenate the bits and regroup into 6-bit chunks:
01001000 01101001 00100001
010010 000110 100100 100001
18 6 36 33Map each value to the Base64 alphabet (index 18 = S, 6 = G, 36 = k, 33 = h) and you get SGkh. Because the input was exactly three bytes, no padding is needed.
In practice you'd never do this by hand. Here's the same thing in a few languages:
// JavaScript (browser)
btoa("Hi!"); // "SGkh"
atob("SGkh"); // "Hi!"
// Node.js (handles binary + encodings properly)
Buffer.from("Hi!", "utf8").toString("base64"); // "SGkh"
Buffer.from("SGkh", "base64").toString("utf8"); // "Hi!"
// Python
import base64
base64.b64encode(b"Hi!") # b'SGkh'
base64.b64decode("SGkh") # b'Hi!'Note the JavaScript caveat: btoa only handles Latin-1 strings and will throw on multi-byte characters. For UTF-8 in the browser you need to encode via TextEncoder first. Node's Buffer avoids that trap entirely.
When you just need to check a value quickly — decode a JWT payload, inspect a data URI, or sanity-check what a service is sending — pasting it into a browser tool is faster than writing a script. Our Base64 Encoder / Decoder handles both directions in the browser, so nothing you paste leaves your machine.
Quick decision guide
- Need to move binary through a text channel? Base64 is the right tool.
- Need to keep something secret? Base64 is not it — encrypt.
- Working with URLs, filenames, or JWTs? Use the URL-safe variant.
- Encoding a large file? Consider whether the 33% bloat is acceptable, or send it as raw multipart instead.
- Crossing language boundaries? Pin down the source character encoding on both ends.
Base64 is one of those primitives that's boring until it breaks. Understanding the three-bytes-to-four-characters rule and the standard-versus-URL-safe split covers the overwhelming majority of real-world problems. Keep the security caveat front of mind and you'll rarely get burned.
Photo by Jake Walker on Unsplash