Skip to main content
~/makemydev/blog/base64-encoding-when-to-use

Base64 encoding — what it is, when to use it, when to avoid it

Base64 Encoder/Decoder#007·8 min read·

Base64 is one of those things developers use constantly without thinking about. You paste a JWT into a debugger and see the payload. You embed a small image in CSS as a data URL. You set an Authorization header for basic auth. All of these use Base64 under the hood. But most people never look at how it actually works, which leads to confusion about what it can and can’t do.

How Base64 works

Base64 takes binary data and represents it using 64 printable ASCII characters: A–Z, a–z, 0–9, +, and /. The equals sign = is used for padding.

The encoding process works on groups of three bytes (24 bits) at a time. Each group of 24 bits is split into four 6-bit chunks. Each 6-bit chunk (values 0–63) maps to one character in the Base64 alphabet. Three input bytes become four output characters.

Take the ASCII string Hi!. In bytes, that’s 0x48 0x69 0x21 — or in binary:

Binary:  01001000 01101001 00100001
Split:   010010 000110 100100 100001
Decimal: 18     6      36     33
Base64:  S      G      k      h

So Hi! encodes to SGkh.

When the input length isn’t divisible by three, the encoder pads the output with = characters. One byte of input produces two Base64 characters plus ==. Two bytes produce three characters plus =. Three bytes produce four characters with no padding. The pattern repeats.

The 33% size overhead

Every three bytes of input become four bytes of output. That’s a 4/3 ratio, meaning Base64-encoded data is roughly 33% larger than the original. For a 1 MB image, the Base64 version is about 1.33 MB.

This matters. Embedding a 200 KB PNG as a data URL in your CSS means your stylesheet grows by ~267 KB of Base64 text. That text isn’t compressible the way the original PNG is, and it blocks rendering until the entire stylesheet is parsed. For small assets (icons under 2–4 KB), the overhead is negligible and you save an HTTP request. For anything larger, serve the file normally.

Where Base64 is the right tool

Data URLs

A data URL lets you embed a file directly in HTML, CSS, or JavaScript without making a separate HTTP request:

data:image/png;base64,iVBORw0KGgoAAAANSUhEUg...

This is useful for tiny images in CSS (spinner icons, 1x1 tracking pixels), SVG sprites, and font subsets. Build tools like Webpack and Vite can automatically inline assets below a size threshold.

Email attachments (MIME)

SMTP was designed for 7-bit ASCII text. Binary attachments — PDFs, images, zip files — can’t be sent as raw bytes because mail servers may corrupt bytes with the high bit set, strip null bytes, or impose line-length limits. MIME solves this by Base64-encoding attachments, turning arbitrary binary into safe ASCII that survives transit through any mail relay.

This is arguably the original use case for Base64. RFC 2045 (1996) defined it specifically for MIME, though the encoding scheme dates back to RFC 1421 (1993) and PEM-encoded certificates.

JWTs

A JSON Web Token has three parts separated by dots: header, payload, and signature. The header and payload are JSON objects encoded with Base64url (a URL-safe variant that replaces + with - and / with _, and drops the padding). Base64url lets the token be passed in URLs, cookies, and HTTP headers without any escaping issues.

The payload is not encrypted. Anyone who has the token can decode it and read the claims. This is by design — the signature protects integrity, not confidentiality.

HTTP Basic Authentication

The Authorization header for Basic auth looks like this:

Authorization: Basic dXNlcjpwYXNzd29yZA==

That Base64 string decodes to user:password. The encoding ensures the credentials survive HTTP header parsing (colons, non-ASCII characters, etc.) without ambiguity. It does not hide them. Anyone intercepting the request can decode the credentials instantly. Basic auth should only be used over HTTPS.

Binary data in JSON and XML

JSON has no binary type. If you need to include a file, a cryptographic key, or a hash in a JSON payload, you Base64-encode it into a string. XML has the same limitation, and the xs:base64Binary type exists for exactly this purpose. APIs like AWS S3 use Base64 to pass MD5 checksums in JSON request bodies.

Base64 is not encryption

This needs to be said clearly: Base64 provides zero security. It’s an encoding, not encryption. There is no key. There is no secret. Decoding is deterministic and instant — run the string through any decoder and you get the original bytes back.

I’ve seen production codebases that “encrypt” API keys by Base64-encoding them before storing them in config files or environment variables. This does nothing. An attacker (or a curious coworker) who finds cGFzc3dvcmQxMjM= can decode it to password123 in under a second. If you need to protect data, use actual encryption (AES-256-GCM, for example) or a secrets manager.

The confusion is understandable. Base64 output looks random and unreadable. But looking unreadable and being unreadable are different things. ROT13 also looks like gibberish. Neither provides security.

Base64url vs standard Base64

Standard Base64 uses + and / as characters 62 and 63. Both have special meanings in URLs ( + becomes a space, / is a path separator). Base64url (RFC 4648 §5) swaps them for - and _, and typically omits padding.

If you’re putting Base64 in a URL, a filename, or a JWT, use Base64url. If you’re using it in a context where the full ASCII range is safe (email bodies, JSON string values, PEM files), standard Base64 is fine.

When to avoid Base64

  • Large files. The 33% size overhead adds up. A 10 MB video becomes 13.3 MB of text. If your transport supports binary (HTTP with Content-Type: application/octet-stream, gRPC, WebSockets in binary mode), send the bytes directly.
  • Images in HTML/CSS.Anything over a few KB should be a separate file. The Base64 version can’t be cached independently, it bloats the document, and it prevents the browser from progressively loading the image.
  • Database storage. Storing Base64 strings in a database column wastes 33% more space than storing raw bytes in a BYTEA or BLOB column. The database also can’t do anything useful with the encoded string — no indexing, no range queries, no content-aware compression.
  • Security.As discussed above: if you’re reaching for Base64 to “hide” something, you’re using the wrong tool.
  • Human-readable config.Base64 strings are opaque blobs. If someone needs to read or edit the value, store it in plain text or hex. Base64 is for when the value is genuinely binary and the transport can’t handle raw bytes.

Base64 in JavaScript

The browser provides btoa() and atob() for encoding and decoding. These work on strings where each character is a single byte (Latin-1). For UTF-8 strings with multi-byte characters, you need an extra step:

// Encode UTF-8 string to Base64
const encoded = btoa(
  new TextEncoder().encode("café ☕")
    .reduce((s, b) => s + String.fromCharCode(b), "")
);

// Decode Base64 back to UTF-8 string
const decoded = new TextDecoder().decode(
  Uint8Array.from(atob(encoded), c => c.charCodeAt(0))
);

In Node.js, use Buffer:

const encoded = Buffer.from("café ☕").toString("base64");
const decoded = Buffer.from(encoded, "base64").toString("utf-8");

Both runtimes also support Base64url via Buffer.from(str, "base64url") in Node, or by manually replacing characters in the browser.

Try it

If you want to quickly encode or decode a string, convert a file to a data URL, or just see what a Base64 blob contains, the Base64 Encoder/Decoder on this site handles all of that in the browser. Paste your text or drop a file, and the conversion happens instantly. Nothing is sent to a server.