Base64 encoding turns binary data into plain ASCII text so it can pass through systems that only expect text. You see it in data: URLs that embed an image, in JWT payloads, in email attachments and in API keys stored in config files.
This guide explains Base64 from the ground up. It covers the problem Base64 solves, the algorithm that turns three bytes into four characters, the standard and URL-safe alphabets from RFC 4648, and what the = padding is for. Most important of all: Base64 is an encoding, not encryption. It hides nothing.
The problem Base64 solves
Computers store everything as bytes. A byte is 8 bits and holds a value from 0 to 255. Text is one way to interpret those bytes. Many systems were built to carry *text*: email bodies, URLs, JSON strings, XML documents, HTTP headers. Give them raw binary, like the bytes of a PNG, a compressed archive or an encryption key, and things break. A null byte can cut a string short. A byte that matches a control character can get mangled. Anything outside printable ASCII may not survive the trip.
Base64 avoids the problem by writing binary data with only 64 characters that every text system accepts: A-Z, a-z, 0-9 and two symbols. The result looks like gibberish, but it is always safe printable text. You can put it in a JSON value, a URL or an email, and it arrives with every byte intact.
How Base64 works, with an example
The name describes the algorithm: work in base 64. Read the input as a stream of bits and regroup it. Binary uses 8 bits per byte. Base64 uses 6 bits per character, because 2^6 = 64. The least common multiple of 8 and 6 is 24, so Base64 reads 3 bytes (24 bits) at a time and writes 4 characters (24 bits).
Here is a Base64 example with the word Man. Its three ASCII bytes are 77, 97 and 110, which in binary is 01001101 01100001 01101110. Line up those 24 bits and cut them into four 6-bit groups:
Text: M a n
ASCII: 77 97 110
Bits: 01001101 01100001 01101110
Regroup: 010011 010110 000101 101110
Value: 19 22 5 46
Base64: T W F uEach 6-bit value, from 0 to 63, is an index into the Base64 alphabet. Index 19 is T, 22 is W, 5 is F and 46 is u, so Man encodes to TWFu. Decoding reverses the process. Map each character back to its 6-bit value, join the bits, and cut them into 8-bit bytes.
// In the browser (and modern Node):
const encoded = btoa("Man"); // "TWFu"
const decoded = atob("TWFu"); // "Man"
// Note: btoa/atob work on binary strings, not Unicode text.
// For arbitrary strings, encode to UTF-8 bytes first:
const bytes = new TextEncoder().encode("café");
const b64 = btoa(String.fromCharCode(...bytes)); // "Y2Fmw6k="Padding: what the '=' means
Base64 works in 3-byte groups, but real data rarely divides evenly by three. When the last group has only 1 or 2 bytes, the encoder pads the output to a multiple of four characters with the = sign. = is never a data character. It only tells the decoder how many bytes the last group held.
- 3 bytes left → 4 characters, no padding (for example
TWFu). - 2 bytes left → 3 characters + one
=(for exampleTWE=forMa). - 1 byte left → 2 characters + two
=(for exampleTQ==forM).
The number of = signs tells the decoder how many trailing bytes to keep. Some formats drop the padding to save characters, most notably JWTs and URL parameters. A decoder can usually work out the length without it, which is why you will see unpadded Base64 in practice.
Standard vs URL-safe Base64 (RFC 4648)
The Base64 format is defined in RFC 4648, which specifies two alphabets. The first 62 characters are the same in both: A-Z, a-z, 0-9. They differ only in the last two symbols and the padding, and that difference matters a lot depending on where the string goes.
| Index 62 | Index 63 | Padding | Safe in URLs? | |
|---|---|---|---|---|
| Standard Base64 | + | / | = | No |
| URL-safe Base64 | - | _ | often omitted | Yes |
Standard Base64 uses + and /, and both cause trouble in a URL. Query strings read + as a space, and / separates path segments. URL-safe Base64 (also called base64url) swaps them for - (hyphen) and _ (underscore), which need no escaping in a URL or filename. The = sign is also reserved in URLs, so URL-safe Base64 usually drops the padding.
Converting between the two is a character swap. Replace + with - and / with _, and strip =, to go URL-safe. Reverse it to go back. If a decoder rejects a string, check for a mismatched alphabet first. The tool below encodes either variant and decodes both.
Base64 is not encryption
This is the most common misconception, and it has real security consequences. Base64 is a reversible, public transformation with no key. Anyone can decode any Base64 string, because no secret is involved. It provides no confidentiality. Encoding a password, API secret or personal data in Base64 does *not* protect it. It only makes it a little less obvious to someone skimming the text.
// "Encoding" a secret protects nothing:
const secret = btoa("hunter2"); // "aHVudGVyMg=="
atob("aHVudGVyMg=="); // "hunter2", anyone can reverse itEncoding changes how data is represented so systems can handle it (Base64, URL encoding, UTF-8). Encryption protects data so only key holders can read it (AES, RSA). If you need confidentiality, you need encryption. Use Base64 for what it is for: moving binary safely through text channels.
Where you will meet Base64
- Data URLs.
data:image/png;base64,iVBORw0KGgo...embeds an image directly in HTML or CSS, with no separate request. - JWTs. The header and payload are base64url-encoded JSON. See the JWT guide.
- Email (MIME). Attachments and non-ASCII bodies are Base64-encoded so they survive text-only mail transport.
- HTTP Basic Auth. The
Authorizationheader carriesbase64(username:password), which is why Basic Auth needs HTTPS. - Binary in JSON. JSON has no binary type, so a byte blob is stored as a Base64 string field.
JSON developers hit that last case most often. JSON has no binary type, so any file bytes, hash or key you want in a payload has to become a Base64 string first and be decoded on the other side.
To inspect a token, embed an asset or check what a Base64 string contains, decode it locally. The tool below runs in your browser and uploads nothing, so you can paste a token without sending sensitive data anywhere.