DEV Community

Snappy Tools
Snappy Tools

Posted on

Base64 Encoding: What It Is, How It Works, and When to Use It

Base64 is one of those things developers use constantly — in JWTs, data URLs, API authentication — without always understanding what it actually does. Here's a clear explanation.

What Base64 is (and isn't)

Base64 is an encoding scheme, not encryption. It converts binary data into a string of 64 printable ASCII characters: A-Z, a-z, 0-9, +, and /, plus = for padding.

The purpose: safely represent binary data in contexts that only handle text. Email attachments, HTML data URLs, and HTTP headers are all text-based — Base64 lets you embed binary content (images, files) in those contexts without corruption.

If you need to encode or decode Base64 in the browser, the Base64 Encoder/Decoder handles both directions instantly.

How it works

Base64 works in three steps:

  1. Take the binary input as bytes (8 bits each)
  2. Group the bits into 6-bit chunks
  3. Map each 6-bit value (0–63) to one of the 64 characters

Example with "Man":

M = 01001101
a = 01100001
n = 01101110

Combined: 010011010110000101101110

Split into 6-bit groups: 010011 | 010110 | 000101 | 101110
Decimal:                    19  |    22  |     5  |    46
Base64:                      T  |     W  |     F  |     u

Result: "TWFu"
Enter fullscreen mode Exit fullscreen mode

Three bytes of input produce exactly four Base64 characters — a 33% size increase.

Padding

Input doesn't always divide evenly into 3-byte groups. = characters pad the output to a multiple of 4 characters:

  • 1 remaining byte → 2 Base64 chars + ==
  • 2 remaining bytes → 3 Base64 chars + =
  • 3 remaining bytes → 4 Base64 chars (no padding)

Base64 in JavaScript

// Browser — btoa() and atob()
const encoded = btoa('Hello, world!');   // "SGVsbG8sIHdvcmxkIQ=="
const decoded = atob('SGVsbG8sIHdvcmxkIQ==');  // "Hello, world!"

// Node.js — Buffer
const encoded = Buffer.from('Hello, world!').toString('base64');
// "SGVsbG8sIHdvcmxkIQ=="

const decoded = Buffer.from('SGVsbG8sIHdvcmxkIQ==', 'base64').toString('utf8');
// "Hello, world!"
Enter fullscreen mode Exit fullscreen mode

Unicode caveat with btoa()

btoa() only handles characters with code points 0–255. For arbitrary Unicode strings:

// Fails for characters outside Latin-1
btoa('')  // throws InvalidCharacterError

// Fix: encode as UTF-8 first
function encodeBase64(str) {
  return btoa(encodeURIComponent(str).replace(/%([0-9A-F]{2})/g,
    (_, p1) => String.fromCharCode(parseInt(p1, 16))
  ));
}

// Or in modern environments:
const encoder = new TextEncoder();
const bytes = encoder.encode('Hello, €');
const base64 = btoa(String.fromCharCode(...bytes));
Enter fullscreen mode Exit fullscreen mode

Node.js Buffer handles UTF-8 correctly without this issue.

URL-safe Base64

Standard Base64 uses + and /, which have special meaning in URLs. URL-safe Base64 replaces them:

  • +-
  • /_
  • = padding is often omitted

This variant is used in JWTs, OAuth tokens, and anywhere Base64 appears in URLs or HTTP headers.

function toBase64Url(base64) {
  return base64.replace(/\+/g, '-').replace(/\//g, '_').replace(/=/g, '');
}

function fromBase64Url(base64url) {
  const padded = base64url.replace(/-/g, '+').replace(/_/g, '/');
  const padding = padded.length % 4 === 0 ? '' : '='.repeat(4 - padded.length % 4);
  return padded + padding;
}
Enter fullscreen mode Exit fullscreen mode

Common uses

Data URLs (embedding images in HTML/CSS)

<img src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAA...">
Enter fullscreen mode Exit fullscreen mode
.icon {
  background-image: url('data:image/svg+xml;base64,PHN2ZyB4bWxucy...');
}
Enter fullscreen mode Exit fullscreen mode

This embeds the image directly in the HTML or CSS file — no extra HTTP request. Useful for tiny icons, but avoid for anything larger than ~1KB (base64 adds 33% overhead and can't be cached separately).

HTTP Basic Authentication

const credentials = btoa('username:password');
fetch('/api/data', {
  headers: {
    'Authorization': `Basic ${credentials}`
  }
});
Enter fullscreen mode Exit fullscreen mode

Basic Auth Base64-encodes the username:password string. Note: this is not encryption — it's trivially reversible. Always use HTTPS.

JWT tokens

The header and payload sections of a JWT are Base64Url-encoded JSON:

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0In0.abc123
^--- header ---^              ^-payload-^  ^-signature-^
Enter fullscreen mode Exit fullscreen mode

Decoding the header: atob('eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9'){"alg":"HS256","typ":"JWT"}

Email attachments (MIME)

Content-Type: application/pdf; name="document.pdf"
Content-Transfer-Encoding: base64

JVBERi0xLjQKJeLjz9MKMSAwIG9iago8PAovVHlwZSAvQ2F0YWxvZwov...
Enter fullscreen mode Exit fullscreen mode

MIME uses Base64 to embed binary attachments in the text-based email format.

Base64 is not encryption

This is worth repeating. Base64 encoding is completely reversible without any key — it's a standard algorithm anyone can reverse. Never use it to protect sensitive data.

Using Base64 to "hide" a password or API key is security theater. An attacker who sees the encoded string can decode it in seconds.

For actual security: use encryption (AES for symmetric, RSA/ECDSA for asymmetric) or hashing (bcrypt/Argon2 for passwords, SHA-256+ for integrity checks).

Size trade-offs

Original: 100 bytes binary
Base64:   136 bytes (33% larger)

Original: 1 MB image
Base64:   1.37 MB
Enter fullscreen mode Exit fullscreen mode

The 33% overhead is predictable: 3 input bytes → 4 output characters. For images and files, this overhead is why external files + HTTP caching is usually better than data URLs beyond small sizes.


Base64 exists to solve a specific problem: safely transmitting binary data through text-only channels. When you see it in JWTs, data URLs, or API credentials, that's what it's doing. It's not compression, not encryption — just a reliable way to represent bytes as printable text.

Top comments (0)