Blog

How to convert images to Base64 (data URIs explained)

What Base64 and data URIs are, when tucking an image straight into your HTML or CSS helps, and when a plain old image file is the better call.

February 10, 2026

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:

Text
data:[<mediatype>][;base64],<data>

So a PNG looks like this:

Text
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:

CSS
.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:

HTML
<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:

CSS
.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:

JavaScript
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:

JavaScript
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

  1. Keep it small. Embed only images of a few kilobytes. Serve anything bigger as a cached file.
  2. 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.
  3. Compress first. Run the image through Squoosh or SVGO before encoding. Smaller input, shorter string.
  4. Check your CSP. Make sure img-src allows data:.
  5. 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-image rule or an HTML img tag.
  • 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.

Need data from a webpage as JSON?

PulpMiner turns any public page into structured JSON and can save it as a live API. See it work on a demo page, no sign-up.

Try it on a demo page