You open a page’s source, find the logo’s img tag, and instead of a file name there’s a wall of gibberish that scrolls on for three screens. That’s not a bug. It’s a Base64-encoded image inside a data URI.
The trick puts the image’s bytes straight into the HTML, CSS or JavaScript, so the browser doesn’t have to fetch a separate file. Sometimes that’s a nice win. Sometimes it quietly makes your site slower. Let’s look at how it works and how to tell which case you’re in.
What is Base64 encoding
An image is binary data, and you can’t paste raw bytes into a text file like HTML. Base64 solves that by spelling the bytes out with 64 safe, printable characters: A–Z, a–z, 0–9, + and /. An = or two may appear at the end as padding.
It works in small chunks. Every three bytes (24 bits) get split into four groups of 6 bits, and each group becomes one character. Three bytes in, four characters out, so the encoded version is always about 33% bigger than the original. Keep that number in mind. It comes back later.
What is a data URI
A data URI (also called a data URL, defined in RFC 2397) is a URL that carries the file itself instead of pointing to where the file lives. The pattern is:
data:[<mediatype>][;base64],<data>So a PNG looks like this:
data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAA...The media type is the file’s MIME type, such as image/png, image/jpeg, image/svg+xml or image/webp. The ;base64 part tells the browser how to read what comes after the comma: as Base64, rather than as percent-encoded text.
When Base64 images make sense
Small icons and UI bits
Every separate file costs a request, and for a tiny image the request can cost more than the image. For something that’s only a few kilobytes, like a search icon, a spinner or a little chevron, the extra 33% hardly matters and you skip the round trip.
CSS background images
An image inside a stylesheet arrives with the stylesheet. It’s ready the moment the styles apply, so there’s no request and no blink while it loads:
.icon-search {
background-image: url("data:image/svg+xml;base64,PHN2ZyB4...");
background-size: contain;
}One catch: the browser won’t draw the page until the CSS has downloaded, so every byte you stuff into it delays the first paint. Keep these images small.
Self-contained HTML files
Say you’re emailing an invoice or a report as a single .html file. With data URIs, every image lives inside that one file, and it opens fine on a laptop in airplane mode with no server in sight.
HTML email, carefully
This is where people expect data URIs to shine, and they mostly don’t. Gmail and several Outlook versions won’t show them at all. Linked images, or images attached and referenced with cid:, are more reliable. Test in the clients your readers actually use before you commit.
Fewer requests on HTTP/1.1
Old-style HTTP/1.1 lets a browser open only about six connections per site, so lots of small images wait in line. Inlining them shortens the queue. HTTP/2 and HTTP/3 send many requests over one connection, so on a modern server this benefit has mostly gone away.
When to avoid Base64 images
Large images
Your 100 KB hero photo turns into about 133 KB of text. The bigger problem is that it now lives inside the HTML or CSS, so the browser can’t cache it on its own. Once an image is more than a few kilobytes, a normal file with good cache headers almost always wins.
Pages that change often
Fix a typo in the HTML and every visitor downloads all the embedded images again, even though they didn’t change. A separate image file would have come straight from the browser cache.
A strict Content Security Policy
A Content Security Policy (CSP) is a header that tells the browser where your page may load things from. If its img-src rule doesn’t include data:, data URI images are blocked. Check before you switch, or you’ll be staring at broken icons wondering what happened.
Server-rendered pages
Big Base64 strings make the HTML document heavier, so it takes longer to download and parse before anything shows up. Separate images can load in parallel and can be lazy-loaded, so they don’t hold up the rest of the page.
The trade-offs in detail
- Size. Base64 adds about 33%. A 3 KB icon becomes about 4 KB. Gzip or Brotli on the document wins some of that back, but the result is still larger than the compressed original.
- Caching. This is the big one. A separate file has its own cache lifetime. An embedded image is cached only as part of its document, and gets downloaded again whenever that document changes.
- Decoding. The browser has to turn the text back into binary before it can draw the image. Negligible for a few icons, but it adds up with many large ones.
- Memory. The browser holds both the text and the decoded image in memory, which matters on a budget phone.
Using Base64 images in HTML
Drop the data URI into the src of an img tag, exactly where a file path would go:
<img src="data:image/png;base64,iVBORw0KGgoAAAA..." alt="Search" width="24" height="24" />It works in srcset too, but responsive images tend to be large, which is exactly when you want separate caching. So you’ll rarely see it.
Using Base64 images in CSS
Anywhere CSS takes a url(), a data URI fits:
.logo {
background-image: url("data:image/svg+xml;base64,PHN2ZyB...");
}
.divider {
border-image: url("data:image/png;base64,iVBORw0...") 30;
}
.cursor-custom {
cursor: url("data:image/png;base64,iVBORw0..."), auto;
}Images inside a stylesheet share its cache lifetime. If your CSS rarely changes, that’s a reasonable middle ground between separate files and embedding in the HTML.
Converting images in code
In the browser, FileReader turns a File (say, from an <input type="file">) into a data URI:
const reader = new FileReader();
reader.onload = () => {
const dataUri = reader.result; // "data:image/png;base64,..."
};
reader.readAsDataURL(file);In Node.js, read the file into a Buffer and ask for Base64:
const fs = require("node:fs");
const base64 = fs.readFileSync("logo.png").toString("base64");
const dataUri = `data:image/png;base64,${base64}`;Both give you the same string. For a one-off, though, a browser tool is quicker than writing either.
Best practices
- Keep it small. Embed only images of a few kilobytes. Serve anything bigger as a cached file.
- Try SVG without Base64. SVG is already text, so you can percent-encode it into a data URI (
data:image/svg+xml,%3Csvg...), which is often smaller than the Base64 version. - Compress first. Run the image through Squoosh or SVGO before encoding. Smaller input, shorter string.
- Check your CSP. Make sure
img-srcallowsdata:. - Measure. Compare both versions in Lighthouse or WebPageTest. Real numbers beat rules of thumb, including the ones in this post.
Convert images with PulpMiner
The free Base64 image converter runs in your browser, so your images never get uploaded anywhere. It works in both directions:
- Encode. Drop in an image or choose a file (PNG, JPEG, GIF, WebP or SVG). You’ll see a preview, the original size and the Base64 size, and you can copy the result as a data URI, raw Base64, a CSS
background-imagerule or an HTMLimgtag. - Decode. Paste a Base64 string, with or without the
data:prefix, to preview the image and download it. A string without a prefix is labelled as PNG.
