Why Your Image Isn't Loading: Understanding Base64 Encoding in 2026
A broken img tag led me down a two-hour rabbit hole that ended with me finally understanding Base64 encoding. This guide covers how Base64 works at the bit level, when to use it versus when it makes things worse, the URL-safe variant, size overhead with real examples, and every use case from JWT tokens to email attachments.
What you'll learn in this guide
- ✅How Base64 encoding works at the bit level — the 3-bytes-in, 4-chars-out algorithm explained clearly
- ✅When Base64 actually helps versus when it makes performance worse — with a size overhead example
- ✅Standard vs URL-safe Base64: why the difference matters for JWT tokens, query strings, and file names
I spent two hours debugging a broken image in an email template. The img tag looked fine. The URL looked fine. Everything looked fine — except the image showed the broken icon on every email client I tested. I checked the server, checked the CDN, checked the HTML structure. Nothing. Then I pasted the src value into a decoder and immediately saw the problem: someone had used standard Base64 with + and / characters in a URL context. Email clients were treating the + as a space and the / as a path separator, mangling the data entirely. Two hours, one character substitution. That was the day I actually learned Base64.
What Base64 Actually Is (And Isn't)
Base64 is a binary-to-text encoding scheme. It takes any binary data — bytes, images, audio, anything — and converts it into a string made of 64 printable ASCII characters. That's the entire point: moving binary data safely through systems that only handle text.
The 64 characters are: A-Z (26), a-z (26), 0-9 (10), + and / (2). That's 64. Plus = for padding. Every binary byte stream can be represented using only these characters. Systems that can't handle arbitrary bytes — email, URLs, HTML attributes, JSON strings — can handle Base64 just fine.
Original text: Hello
Bytes (ASCII): 72 101 108 108 111
Binary: 01001000 01100101 01101100 01101100 01101111
Base64 output: SGVsbG8=
Original text: Hi!
Base64 output: SGkh
Original JSON: {"id": 1}
Base64 output: eyJpZCI6IDF9Try this tool now:
Base64 Encoder/Decoder →How the Algorithm Actually Works
Base64 processes input in 3-byte (24-bit) groups. It splits each 24 bits into four 6-bit segments, then maps each 6-bit value to one of the 64 characters. This is why every 3 input bytes produce exactly 4 output characters.
Encoding 'Man' (3 bytes: 77, 97, 110):
Binary: 01001101 01100001 01101110
└──────────────────────┘
24 bits total
Split into 4 groups of 6 bits:
010011 | 010110 | 000101 | 101110
19 | 22 | 5 | 46
T | W | F | u
Result: 'TWFu'
Padding example — encoding 'Ma' (2 bytes):
Binary: 01001101 01100001 [missing byte → pad with 0s]
010011 | 010110 | 000100 | (padding)
T | W | E | =
Result: 'TWE='
Padding example — encoding 'M' (1 byte):
Binary: 01001101 [2 missing bytes → pad with 0s]
010011 | 010000 | (padding) | (padding)
T | Q | = | =
Result: 'TQ=='The padding = characters at the end tell the decoder how many bytes of actual data to expect. One = means the last group had 2 real bytes. Two == means the last group had only 1 real byte. No padding means the input was a perfect multiple of 3 bytes.
Use Cases: When Base64 Helps vs When It Hurts
Base64 is a tool, not a universal solution. Here is a clear breakdown of when it helps and when it makes things worse:
| Use Case | Base64? | Why |
|---|---|---|
| Small icons in HTML/CSS (data URIs) | Yes — good | Eliminates an HTTP request; 33% overhead is outweighed by saving a round trip |
| JWT token header + payload | Yes — required | JWT spec mandates Base64url encoding; this is non-negotiable |
| Email attachments (MIME) | Yes — required | SMTP only transports 7-bit ASCII; Base64 makes binary safe for email |
| Images in JSON API responses | Sometimes — small only | OK for thumbnails < 5KB; for anything larger, use a URL reference instead |
| Large images on a webpage | No — bad | 33% size overhead + blocks browser parsing; use a CDN URL instead |
| Passwords or secrets in config | Never — wrong tool | Base64 is not encryption; anyone can decode it in seconds |
| Binary files in URL query strings | Yes — use URL-safe variant | Standard Base64 breaks in URLs; must use URL-safe version with - and _ |
The 5KB rule for data URIs
If your image is under 5KB, embedding it as a Base64 data URI in CSS usually saves a network round trip and makes the page faster. Above 5KB, the overhead outweighs the benefit — serve it as a separate file from a CDN instead. For SVGs, inlining the raw SVG markup is almost always better than Base64-encoding it.
Standard Base64 vs URL-Safe Base64
Standard Base64 uses + and / in its alphabet. Both characters are reserved in URLs and have special meanings in HTML forms and query strings. This causes exactly the kind of silent corruption I described at the start.
Standard Base64 alphabet:
A-Z a-z 0-9 + / (with = padding)
URL-safe Base64 alphabet (RFC 4648 §5):
A-Z a-z 0-9 - _ (with = padding, sometimes omitted)
Example — same data, different encoding:
Standard: SGVsbG8+V29ybGQ= ← the + breaks in a URL
URL-safe: SGVsbG8-V29ybGQ= ← safe everywhere
When to use URL-safe:
- JWT tokens (header.payload.signature — all URL-safe Base64)
- Query string parameters (?token=SGVsbG8-V29ybGQ)
- Filenames and path components
- Anything that goes in an HTML attribute value
When standard Base64 is fine:
- Email MIME attachments
- HTTP response body (not URL)
- Data URIs in CSS/HTML (src="data:image/png;base64,...")Base64 is not encryption — not even close
Base64 looks like scrambled text, and people sometimes assume it provides security. It does not. Any developer can decode Base64 in under 10 seconds using any online tool. Encoding credentials, API keys, or personal data in Base64 and calling it 'secure' is a security vulnerability, not a feature. If you need to protect sensitive data, use actual encryption: AES-256 for symmetric, RSA for asymmetric, bcrypt/argon2 for passwords. Base64 is purely a transport format.
Calculating the Size Overhead
The 33% overhead rule is worth understanding precisely. For every 3 input bytes, Base64 outputs 4 characters. Each character is 1 byte in ASCII. So the ratio is 4/3 = 1.333, meaning Base64 output is always approximately 33% larger than the input.
Size overhead calculation:
Input size Base64 size Overhead
1 KB (1,024 B) 1,368 B +344 B (+33.6%)
10 KB 13,336 B +3,336 B (+33.4%)
100 KB 133,400 B +33,400 B (+33.4%)
1 MB 1.33 MB +334 KB (+33.4%)
5 MB 6.67 MB +1.67 MB (+33.4%)
Real example — a 200x200 PNG icon:
Original file: ~15 KB
Base64 encoded: ~20 KB
Extra bytes: ~5 KB
HTTP request saved: 1
Typical HTTP round trip (fast network): 20-50ms
Verdict: worth inlining. 5KB overhead is much cheaper than 20-50ms.
Real example — a hero image:
Original file: ~250 KB
Base64 encoded: ~333 KB
Extra bytes: ~83 KB
HTTP request saved: 1
Typical HTTP round trip: 20-50ms
Verdict: NOT worth inlining. 83KB of extra download is much worse than one cached request.FAQ
Frequently Asked Questions
How do I encode/decode Base64 in JavaScript?
Browser: btoa() encodes and atob() decodes. These handle standard Base64 only. For URL-safe Base64, replace + with - and / with _ after encoding, and reverse before decoding. For binary data (files, images), use FileReader or Uint8Array with manual conversion — btoa() only handles ASCII strings correctly. Node.js: use Buffer.from(string).toString('base64') to encode and Buffer.from(base64string, 'base64').toString('utf8') to decode.
Why does my Base64 string end with == sometimes?
The = characters are padding. Base64 works on 3-byte groups, and most inputs aren't a perfect multiple of 3 bytes. One = means the last group needed 1 extra byte of padding. Two == means it needed 2 extra bytes. No = means the input length was divisible by 3. The padding ensures the decoder knows exactly how many bytes the original data contained.
What's the difference between Base64 and hex encoding?
Both convert binary data to text, but they use different alphabets and have different size overheads. Hex uses 16 characters (0-9, a-f) and represents each byte as 2 hex digits, doubling the size. Base64 uses 64 characters and represents 3 bytes as 4 characters, increasing size by 33%. Base64 is more compact. Hex is more readable for low-level binary inspection (like checksums, memory addresses, color codes). Use Base64 for data transport; use hex for human-readable binary representations.
Can Base64 encode images, PDFs, or any file?
Yes, Base64 can encode any binary data regardless of type. The encoding process doesn't care what the bytes represent — it just converts them to the 64-character alphabet. This is why it's used for email attachments (MIME): the protocol can attach any file type by Base64-encoding the raw bytes. The receiving client decodes it back to the original bytes and saves it with the correct file extension.
Why does JWT use Base64 specifically?
JWT (JSON Web Token) uses Base64url encoding (the URL-safe variant) for two reasons. First, JWT tokens travel in HTTP headers, URL parameters, and cookies — all text-only environments. Base64 makes the binary signature and JSON payload safe for those contexts. Second, the token needs to be compact. Hex encoding would be twice as large. Base64url is the most efficient text-safe encoding. Important: JWT encodes the payload — it does NOT encrypt it. Anyone can decode and read the payload. The signature only proves it wasn't tampered with.
Base64 Encoder/Decoder
Encode or decode any text or file to Base64 — standard and URL-safe variants supported
Encode / Decode Now →▶Try the tools from this article
Minjae
Developer & tech writer. Deep dives into dev tools and file conversion technology.
Found this helpful? Get new guide alerts
No spam. Unsubscribe anytime. · By subscribing, you agree to our Privacy Policy.