Base64 explained: what it is, and when to use it

PromotedAdvertise
2026-08-29·Developer reference·2 min read·by Sourabh Singh

Base64 explained: what it is, and when to use it

How base64 encoding works, why it makes data 33% larger, the difference between base64 and base64url, and the one case where inlining is worth it.

Base64 explained: what it is, and when to use it

Base64 encodes binary data as 64 safe ASCII characters, making it about 33% larger. It is an encoding, not encryption - anyone can decode it instantly. Use it only where binary cannot travel: data URIs, email attachments, and JWT segments.

Base64 turns arbitrary binary data into text using 64 characters that survive any transport: A–Z, a–z, 0–9, + and /, with = as padding.

Need to convert something right now? The base64 encoder and decoder handles UTF-8 and base64url in your browser.

How it works

Take three bytes - 24 bits. Split them into four groups of six bits. Each 6-bit group indexes into the 64-character alphabet.

Input:   M         a         n
Bits:    01001101  01100001  01101110
Regroup: 010011  010110  000101  101110
Output:  T       W       F       u

Three bytes in, four characters out. That ratio is the whole story: base64 is always 33% larger than the input, plus padding.

When the input length is not a multiple of three, the last group is padded with = so the output length stays a multiple of four.

Free toolBase64 Encode / DecodeEncode text to base64 or decode it back, with full UTF-8 support and automatic handling of base64url input. Runs entirely in your browser.

base64 vs base64url

The standard alphabet uses + and /, both of which have meaning in URLs - + can be read as a space, / as a path separator.

base64url swaps them: + becomes -, / becomes _, and the trailing = padding is usually dropped entirely.

This is why decoding a JWT with a plain base64 decoder sometimes fails. JWTs use base64url. The fix is two replacements and re-adding padding:

const decode = (segment) => {
  const padded = segment.replace(/-/g, "+").replace(/_/g, "/");
  return atob(padded + "=".repeat((4 - (padded.length % 4)) % 4));
};

The UTF-8 trap in JavaScript

btoa and atob operate on binary strings, one byte per character. Give btoa anything outside Latin-1 and it throws; atob on multi-byte content returns mojibake.

Correct round trip:

const encode = (text) =>
  btoa(String.fromCharCode(...new TextEncoder().encode(text)));

const decode = (b64) =>
  new TextDecoder().decode(
    Uint8Array.from(atob(b64), (c) => c.charCodeAt(0)),
  );

Skip the TextEncoder step and every accented character, emoji and non-Latin script breaks.

Base64 is not encryption

It provides zero confidentiality. Anyone can decode it in one line, and every browser has a decoder built in.

Things it does not do: hide an API key in your bundle, protect a config value, or make a token safe to log. If you are reaching for base64 to conceal something, you want encryption instead.

When inlining is worth it

Base64 in a data URI removes a network request at the cost of 33% more bytes and no caching.

Worth it: tiny assets under about 2KB - a small icon, a placeholder blur - where a round trip costs more than the extra bytes.

Not worth it: anything larger. A 100KB image becomes 133KB, cannot be cached separately, blocks the CSS or HTML it is embedded in from being parsed, and has to be re-downloaded whenever that file changes.

The number that decides it is round-trip latency versus transferred bytes. On HTTP/2 with many parallel requests, the round-trip cost collapsed and the case for inlining got much weaker than it was a decade ago.

If you are base64-encoding something to put in a JSON payload, check whether you can send it as a separate binary upload instead. You are paying a third more bandwidth for the convenience of one request.

Launch it where the numbers are checked

RankCert ranks products on domain control we verify ourselves. Listing is free.

Submit a product - free

Tools from this guide