If you've seen a long string of letters and numbers ending in = or ==, you've met Base64. It's everywhere on the web — in emails, JSON APIs, data URLs and login tokens — and it's often mistaken for encryption.
What Base64 is for
Many systems were built to carry text, not arbitrary bytes: email bodies, JSON, HTML attributes, URLs, HTTP headers. Base64 rewrites any data — an image, a PDF, text in any language — using only 64 safe characters: A–Z, a–z, 0–9, + and /. The data survives the trip untouched and can be turned back into the exact original bytes.
Typical uses:
- Email attachments. Every file you attach is sent as Base64 inside the message.
- Data URLs. A small icon embedded in CSS or HTML as
data:image/png;base64,iVBORw0…. - APIs. Binary data (a signature image, a file) inside a JSON field.
- Login tokens. The three parts of a JWT are Base64URL-encoded JSON.
How it works, in one example
Base64 takes the input 3 bytes at a time (24 bits), cuts them into four groups of 6 bits, and writes each group as one of the 64 characters. Three bytes in, four characters out.
| Text | Base64 |
|---|---|
Hi | SGk= |
Hello | SGVsbG8= |
Hello! | SGVsbG8h |
Școală | yJhjb2FsxIM= |
👋 | 8J+Riw== |
"Hello!" is exactly 6 bytes, so it needs no padding. "Școală" looks like 6 letters but is 8 bytes in UTF-8 (each of Ș and ă takes two bytes), which is why it isn't a multiple of three and ends in =.
Why it ends in = or ==
The = signs are padding. When the input isn't a multiple of 3 bytes, the last group is filled up: one byte left over gives ==, two bytes give =. Padding carries no data, and some systems (JWTs, many URLs) leave it out entirely — a decoder can work out the length without it. Our Base64 decoder accepts both.
How much bigger does it make data?
Four characters for every three bytes: 33% larger, plus up to two padding characters. A 1 MB image becomes about 1.33 MB of Base64 text (1,398,104 characters). That's fine for a small icon in a stylesheet, but a poor trade for large photos — the browser can't cache an inlined image separately, and the page itself gets heavier.
URL-safe Base64 (Base64URL)
+ and / have special meanings in URLs and file names, so a variant replaces them with - and _ and usually drops the padding. That's what JWTs use: the header {"alg":"HS256","typ":"JWT"} becomes eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9 — which is why almost every JWT starts with eyJ. The emoji above is 8J+Riw== in standard Base64 and 8J-Riw in Base64URL. You can paste either form into the decoder.
The "outside of the Latin1 range" error
If you've tried btoa("Școală") in a browser's JavaScript console, Chrome answers: Failed to execute 'btoa' on 'Window': The string to be encoded contains characters outside of the Latin1 range. btoa only accepts characters with codes up to 255, so letters like Ș, ț, emoji or anything outside Western European Latin fail.
The fix is to encode the text as UTF-8 bytes first:
const toBase64 = (text) =>
btoa(String.fromCharCode(...new TextEncoder().encode(text)));
const fromBase64 = (b64) =>
new TextDecoder().decode(Uint8Array.from(atob(b64), (c) => c.charCodeAt(0)));
toBase64("Școală"); // "yJhjb2FsxIM="
Our encoder does exactly this, so text in any language round-trips correctly.
Base64 is not encryption
Anyone can decode Base64 instantly — it hides nothing. If a password, API key or personal data is "only Base64", it is effectively in plain text. To protect data, encrypt it; to check that a file hasn't changed, use a hash (see our hash generator).
You can try all the examples above in Base64 Encode / Decode. It runs in your browser, so the text you paste is never sent anywhere.
