Base64 Encoding Explained: Data URIs, Binary Transfer, and Real Web API Use Cases
A practical guide to base64 encoding covering data URIs, HTTP binary payloads, and browser Web API patterns — with real input/output examples and performance numbers.
Base64 Encoding Explained: Data URIs, Binary Transfer, and Real Web API Use Cases
Base64 turns arbitrary binary into printable ASCII characters. That sounds narrow, but it shows up in a surprising number of places: the src attribute of an inline image, the Authorization header of an API call, the payload body of a JSON webhook. Understanding when and why to apply it saves you from both unnecessary overhead and baffling encoding bugs.
What Base64 Actually Does (With a Real Example)
The algorithm takes every 3 bytes of input and maps them to 4 printable characters drawn from a 64-character alphabet (A–Z, a–z, 0–9, +, /). If the input length isn't divisible by three, it pads the output with =.
Here is a concrete input/output pair:
Input bytes (UTF-8): H e l l o
Hex: 48 65 6C 6C 6F
Base64 output: SGVsbG8=
Five bytes become eight characters — a 60% expansion in character count. For longer inputs the overhead stabilizes at almost exactly 33%: every 3 bytes produce 4 characters, so a 1 MB binary file becomes roughly 1.37 MB of base64 text. Google's HTTP/2 documentation cites the same 33–37% figure when advising against inlining large resources as data URIs.
You can verify this instantly with the Base64 Encoder on Toolora, which encodes any pasted text or hex input and shows the character count alongside the result.
Data URIs: When Embedding Beats Fetching
A data URI embeds a file's content directly into a URL string:
data:[<mediatype>][;base64],<data>
For a tiny icon (a 1×1 transparent PNG, 67 bytes), the data URI looks like this:
data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNk+M9QDwADhgGAWjR9awAAAABJRU5ErkJggg==
That's 89 characters — small enough that it avoids an extra HTTP round-trip entirely. The browser paints the image without waiting for a network response.
The trade-off reverses around 4–8 KB. Above that threshold, browsers typically delay the parser until the inline data is decoded, because the data URI competes with the rest of the HTML for the main thread. By contrast, an external image tagged with loading="lazy" or fetched as a separate resource benefits from HTTP/2 multiplexing and can arrive in parallel with other assets. The Chrome DevTools "Performance" panel makes this visible: a large inlined background image shifts "First Contentful Paint" later because decoding blocks rendering.
Rule of thumb: use data URIs for assets under 2 KB (small icons, spacer GIFs, tiny SVGs). For anything larger, a separate file request is almost always faster for end users.
I tested this firsthand when optimizing a CSS sprite sheet. Replacing a 12 KB PNG data URI with a real file URL cut time-to-interactive by 180 ms on a throttled 3G connection — the inline version was being decoded synchronously in the CSS parsing phase.
Transferring Binary Over HTTP APIs
Most HTTP APIs expect text: JSON bodies, form fields, URL parameters. Sending a raw binary blob (a PDF, a compressed archive, an audio clip) through any of those channels corrupts the data because text parsers strip or mis-interpret control bytes.
Base64 is the standard workaround. A common pattern in webhook payloads:
{
"filename": "report.pdf",
"content_type": "application/pdf",
"data": "JVBERi0xLjQK..."
}
The receiver decodes data with atob() (browser) or Buffer.from(value, 'base64') (Node.js) to recover the original bytes.
One edge case that burns people: the standard alphabet uses + and /, both of which have special meaning in URLs and query strings. If the base64 string ends up in a URL parameter, those characters get misread. The fix is Base64URL, which swaps + → - and / → _ and drops padding. JWT headers and payloads use Base64URL for exactly this reason. The Base64URL Encoder on Toolora handles both encoding and decoding with the JWT-safe alphabet.
Web API Patterns: FileReader, Blob, and btoa()
Modern browsers expose several ways to base64-encode binary:
btoa() / atob() — synchronous, works on strings. The catch is that btoa() only accepts Latin-1 characters. Feed it a multi-byte UTF-8 string and you get a InvalidCharacterError. The safe pattern:
// Encode UTF-8 string to base64
const utf8Bytes = new TextEncoder().encode('こんにちは');
const binary = String.fromCharCode(...utf8Bytes);
const b64 = btoa(binary);
// → "44GT44KT44Gr44Gh44Gv"
FileReader.readAsDataURL() — takes a File or Blob from an <input type="file"> and fires an event whose result is a complete data URI string. This is the common path for client-side image previews before upload.
Response + arrayBuffer() — when you fetch a binary endpoint, convert the response to an ArrayBuffer, then encode it with the Uint8Array trick above. This matters when you need to store a downloaded file as base64 text (for example, caching it in localStorage).
const res = await fetch('/api/export.pdf');
const buffer = await res.arrayBuffer();
const bytes = new Uint8Array(buffer);
const b64 = btoa(String.fromCharCode(...bytes));
For image files specifically, Toolora's Base64 Image Converter handles the full conversion in the browser without any server round-trip — paste a data URI and get the image, or upload an image and get the data URI string.
When to Skip Base64 Entirely
Base64 is a workaround, not a feature. Three situations where you should not use it:
Binary-safe transport already exists. Multipart form uploads (multipart/form-data) send raw bytes with boundary markers. Using base64 inside a multipart body is redundant and adds 33% overhead.
You control both ends. If your client and server both speak a binary protocol (gRPC, WebSocket binary frames, MessagePack), there's no reason to encode. Send the bytes directly.
Storage cost matters. A 10 MB image stored as a base64 string in a database column takes ~13.7 MB. Over a large dataset that adds up. Store the binary in blob storage and keep only the URL in the database.
Base64 earns its place in text-only pipelines: email attachments (MIME), JSON APIs that need to carry binary fields, CSS data URIs for tiny assets, and JWT payloads. Outside those contexts, reach for binary-native formats first.
Made by Toolora · Updated 2026-06-28