ToolzYard Blog

Developer guides and tutorials

Encoding Guide

Base64 Encoding Explained: How It Works and When to Use It

Published: June 26, 2026 • Updated: July 10, 2026 • By , Founder of ThreeWorks

If you have ever opened an API response, an email source, or an HTML file and seen a long block of seemingly random letters, numbers, plus signs, and slashes ending in one or two equals signs, you have already met Base64. It is one of the most widely used encoding schemes on the web, and yet it is also one of the most misunderstood. This guide explains exactly what Base64 is, how the algorithm works on a byte level, why the output is always larger than the input, and when you should and should not reach for it.

What is Base64?

Base64 is a binary-to-text encoding scheme. Its job is to take arbitrary binary data — an image, a file, an encryption key, raw bytes of any kind — and represent that data using only a small, safe set of 64 printable ASCII characters. The name comes directly from that alphabet: there are 64 symbols, so each Base64 character carries exactly 6 bits of information (because 2 to the power of 6 is 64).

The crucial point to understand up front is that Base64 is not encryption and not compression. It does not hide your data and it does not make it smaller. It simply re-packages bytes so they can travel safely through systems that were designed to handle text, such as URLs, JSON fields, email headers, and HTML attributes.

Why does Base64 exist?

Many older and even modern protocols are "text-safe" but not "binary-safe." Email, for instance, was historically built to transmit 7-bit ASCII text. If you tried to paste raw binary bytes directly into an email body, certain byte values would be interpreted as control characters, line breaks, or message terminators, corrupting the data. Base64 solves this by guaranteeing that every output character is a harmless, printable symbol that no transport layer will mangle.

You will find Base64 quietly doing this job in many places:

  • Embedding small images directly in HTML or CSS using data: URLs
  • Encoding email attachments via the MIME standard
  • Carrying binary payloads inside JSON, which only supports text values
  • Storing keys, certificates, and tokens in configuration files
  • The payload and header segments of a JSON Web Token (JWT)

The 64-character alphabet

Standard Base64 uses the following 64 characters, plus = as a padding character:

  • Uppercase letters A–Z (values 0–25)
  • Lowercase letters a–z (values 26–51)
  • Digits 0–9 (values 52–61)
  • The symbols + and / (values 62 and 63)

Standard Base64 (defined in RFC 4648) is safe for email and MIME, but it is actively dangerous in a URL, and this is one of the most common Base64 bugs in the wild. When a browser or server URL-decodes a query string, + is interpreted as a literal space, and / is a path separator. So a token encoded with standard Base64 can silently mutate the moment it travels through a URL: a + in your ciphertext becomes a space on the other side, the bytes no longer decode, and you get an intermittent "invalid signature" failure that only shows up for the ~1 in 32 tokens that happen to contain a + or /.

That is exactly why RFC 4648 §5 defines a second alphabet, base64url, which replaces + with - and / with _, and usually drops the = padding as well. Anything that lives in a URL — JWT header and payload segments, OAuth state parameters, signed download links — uses base64url for this reason. The two alphabets are not interchangeable: decode a base64url string with a strict standard-Base64 decoder and it will reject the - and _ characters outright.

How Base64 encoding works, step by step

The algorithm regroups bits. Normal bytes are 8 bits each, but Base64 characters represent 6 bits each. So Base64 takes three bytes (24 bits) of input at a time and splits those 24 bits into four groups of 6 bits, then maps each 6-bit group to a character from the alphabet.

Let us encode the three-letter word Cat.

Step 1: Convert each character to its byte value

C = 67  = 01000011
a = 97  = 01100001
t = 116 = 01110100

Step 2: Join the bits into one 24-bit stream

010000 11 0110 0001 01 110100
→ 01000011 01100001 01110100

Step 3: Re-split into four 6-bit groups

010000  110110  000101  110100
  16      54       5       52

Step 4: Map each value to the alphabet

16 → Q
54 → 2
5  → F
52 → 0

"Cat"  →  "Q2F0"

And that is the whole trick. Three input bytes always become four output characters. You can verify this instantly with the Base64 Encoder / Decoder — paste Cat and you will get Q2F0 back.

What is the padding (=) for?

The neat 3-bytes-to-4-characters mapping only works when the input length is a multiple of three. When it is not, Base64 pads the final group with = characters so the output length is always a multiple of four. The rule is simple:

  • If the last block has 1 leftover byte, the output ends with ==
  • If the last block has 2 leftover bytes, the output ends with =
  • If there are no leftover bytes, there is no padding
"A"   → "QQ=="
"AB"  → "QUI="
"ABC" → "QUJD"

Why is Base64 about 33% larger?

This is the single most important practical fact about Base64. Every 3 bytes of input become 4 bytes of output. That is a 4:3 ratio, which means the encoded result is roughly 33% larger than the original (before counting any padding or line breaks).

Input size Base64 output size Overhead
3 bytes 4 bytes +33%
1 KB ~1.37 KB +33%
1 MB image ~1.37 MB +33%

This is why embedding large images as Base64 data URLs is usually a bad idea: you inflate the page weight by a third and lose the ability to cache the image separately. For tiny icons it can be a net win because you save an HTTP request; for anything large, link to the real file instead.

Base64 is not security

Because Base64 output looks scrambled, people sometimes treat it as a way to "hide" passwords or secrets. This is a serious mistake. Base64 is completely reversible by anyone, with no key and no effort — decoding is a one-click operation. Storing a password as Base64 offers exactly zero protection.

If you need to protect data so that only authorized parties can read it, you need encryption (such as AES). If you need to verify integrity or store passwords safely, you need hashing (such as SHA-256, ideally with a salt). We compare all three in our guide on hashing vs encryption.

When should you use Base64?

Reach for Base64 when you need to move binary data through a text-only channel. Good fits include:

  • Including a tiny inline image, font, or SVG in CSS to save a request
  • Putting binary content inside a JSON field that must remain valid text
  • Encoding credentials for HTTP Basic Auth headers
  • Transporting certificates and keys in PEM-style files

Avoid Base64 when you actually want smaller data (use compression), when you want secrecy (use encryption), or when you are tempted to inline large media files into HTML.

The btoa() Unicode bug every JS developer hits

In the browser the built-in functions are btoa() to encode and atob() to decode. They look convenient, but they carry a sharp edge: they operate on Latin-1 "binary strings," not Unicode. Each character must fit in a single byte (code points 0–255). Feed btoa() anything outside that range — an emoji, a curly quote, an accented name like José — and it throws:

btoa("José")
// ▶ Uncaught DOMException: Failed to execute 'btoa'
//   InvalidCharacterError: The string to be encoded
//   contains characters outside of the Latin1 range.

This is not a bug in your input; it is btoa() refusing to guess how to turn a multi-byte character into bytes. The correct fix is to encode the text to UTF-8 bytes first with TextEncoder, then Base64 those bytes — never hand a raw string to btoa():

// Encode any Unicode text safely
const bytes = new TextEncoder().encode("José 👋");
const b64 = btoa(String.fromCharCode(...bytes));

// Decode back to text
const back = new TextDecoder().decode(
  Uint8Array.from(atob(b64), c => c.charCodeAt(0))
);

Modern runtimes also add Uint8Array.prototype.toBase64() and a matching fromBase64(), which skip the string dance entirely and take a base64url option. In Python the base64 module works on bytes, so you encode your string with .encode("utf-8") first; and most shells ship a base64 command-line tool for quick one-offs.

Watch for MIME line wrapping

One more gotcha when Base64 comes from email, PEM certificates, or older tooling: the MIME variant (RFC 2045) wraps the output into lines of 76 characters separated by CRLF. Those line breaks are whitespace, not data — but a strict decoder that was written to reject non-alphabet characters will choke on the newlines and fail. If you are decoding a PEM key or an email attachment by hand, strip the whitespace first (or use a decoder that tolerates it). This is why copy-pasting a certificate body sometimes "just works" and sometimes returns garbage depending on which library you use.

Try it without writing code

When you just need a quick, reliable result, these browser-based tools handle it instantly and keep your data on your own machine:

Conclusion

Base64 is a simple, elegant solution to a specific problem: representing binary data using safe, printable text. It works by regrouping bits into 6-bit chunks, it always grows the data by about a third, and it is fully reversible — which is exactly why it must never be confused with encryption. Use it to transport binary safely through text channels, keep large media out of data URLs, and reach for encryption or hashing whenever real protection is the goal.

Frequently Asked Questions

Is Base64 encryption?

No. Base64 is a reversible encoding, not encryption. Anyone can decode it instantly without a key, so storing a password or token as Base64 offers exactly zero protection. Use AES-style encryption for secrecy and salted SHA-256 for storing passwords.

Why does Base64 end with one or two equals signs?

The equals signs are padding that keep the output length a multiple of four. One leftover input byte produces ==; two leftover bytes produce a single =; an input length that is already a multiple of three has no padding at all.

How much larger does Base64 make data?

About 33% larger, because every 3 bytes of input become 4 characters of output. Note that this grows the data — Base64 is not compression, so it is the wrong choice when your goal is a smaller payload.

Why can't I use standard Base64 in a URL?

Because + decodes to a space and / is a path separator, standard Base64 gets silently corrupted when it passes through a URL. Use the base64url alphabet (RFC 4648 §5), which swaps those characters for - and _. It is the variant used in JWTs, OAuth state, and signed links.

Why does btoa() throw an error on emoji or accented text?

Because btoa() only accepts Latin-1 characters (bytes 0–255) and throws InvalidCharacterError on anything outside that range. Encode the string to UTF-8 bytes with TextEncoder first, then Base64 those bytes — do not pass a Unicode string straight to btoa().