Base64 turns a file into text so it can travel somewhere that only accepts text: a stylesheet, an HTML attribute, a JSON field, an email body. For images that means a data URI - the image stops being a separate file and becomes part of the document showing it. The conversion is one click in the image to Base64 converter. Whether to do it at all is the part worth thinking about, because the cost is not the one most people quote.
What Base64 actually does to the bytes
Base64 reads your file three bytes at a time. Three bytes is 24 bits, split into four groups of six, and each six-bit group picks one character from an alphabet of 64 - A-Z, a-z, 0-9, plus and slash. Every 3 bytes become 4 characters, so the encoded length is exactly 4 x ceil(n / 3).
That rounding up is where the = signs come from: one leftover byte gives two characters plus ==, two leftover bytes give three plus =. It is also why a string whose length leaves a remainder of one when divided by four is impossible - no whole number of bytes produces it, so such a string is truncated, not merely unpadded.
A worked example, using this site's own social image. The file is 79,049 bytes: 79,049 / 3 is 26,349.67, rounded up to 26,350, times 4 gives 105,400 characters - exactly what the encoder produces. With the data:image/png;base64, header, 105,422 characters stand in for a 77 KB file. That is the famous 33%.
The 33% is not what it costs you
Almost nothing is served uncompressed, and Base64 compresses unusually well - it uses only 64 of the 256 possible byte values, so much of every byte is predictable padding gzip removes again. Gzipped, that same PNG is 69,900 bytes and its Base64 data URI is 74,333. Over the wire the overhead is about 6%, not 33%.
So size is rarely the reason to avoid inlining. Caching is. A data URI is not a file: no URL, no ETag, no cache entry of its own. It is re-downloaded inside every document carrying it and cannot be shared between two pages that both use it. Worse, it lands in the file the browser needs earliest - a 200 KB image inlined into a stylesheet delays every style on the site, because CSS blocks rendering.
When inlining is worth it
Inline when the request you save costs more than the bytes you add:
- Small and decorative - an icon, a bullet, a chevron, a repeating texture. A few kilobytes at most.
- Used on nearly every page, so one copy in the shared stylesheet beats duplicating it everywhere.
- Changing at the same rate as the file it lives in - an updated logo forces the whole stylesheet to be re-downloaded.
- Going somewhere that cannot hold a second file - a single-file HTML export, a Markdown document meant to survive being moved.
Do not inline photographs, hero images, anything above a few kilobytes, or anything a second page might also want. The old advice to inline aggressively came from the HTTP/1.1 era, when six connections per host made an extra request genuinely hurt. Over HTTP/2 requests share one connection, so the threshold has come down, not up.
One trap is specific to email: several major clients, Gmail among them, refuse to render a data URI in an img tag, so an image that looks perfect in your browser arrives as a broken box. Email images belong on a server or a CID attachment.
The anatomy of a data URI
A data URI has four pieces: the data: scheme, a media type, an optional ;base64 marker, and a comma followed by the payload. So data:image/png;base64,iVBORw0KGgo... reads as "here is some image/png, Base64 encoded". Drop the ;base64 and the payload is read as percent-encoded text instead - which matters more than it sounds.
Where it goes depends on the destination: in CSS, background-image: url("data:..."); in HTML, an img src attribute; in Markdown, the ordinary image syntax. An API field or a YAML secret usually wants the raw Base64 with no header at all. The converter emits each of these, and for the HTML form fills in the real width and height, which stops the page jumping as the image decodes.
An SVG should not be Base64 at all
SVG is the one image format that is already text. Wrapping it in Base64 encodes text as text, pays a third extra, and throws away two things worth keeping.
The alternative is a percent-encoded data URI, which escapes only what would break a URI or terminate a CSS url() - angle brackets, quotes, the hash in a colour - and leaves the rest alone. On raw character count the two are close, since whitespace becomes %20. Compressed they are not: this site's social SVG is 846 bytes gzipped percent-encoded against 1,581 as Base64. That is 46% smaller, and it is the number that matters, because Base64 shreds exactly the repetition gzip feeds on.
The second advantage is that it stays readable. A percent-encoded icon is still recognisably SVG, so you can change fill='%23e11d48' in place; the Base64 version must be decoded, edited and re-encoded. Two tricks shrink it further: switch the attributes to single quotes so the URI sits inside double quotes unescaped, and strip whitespace between tags - though not inside a text element, where it is rendered.
Why pasted Base64 so often refuses to decode
A plain atob() throws on most real-world strings, nearly always for one of four reasons:
- Line breaks. MIME encoders wrap at 76 columns, so Base64 from an email header or a PEM certificate arrives as many short lines.
- The URL-safe alphabet. JWTs and many APIs use - and _ for + and /, which is valid Base64url but not valid Base64.
- Missing padding. Many encoders drop the trailing = signs because the length implies them, and strict decoders then refuse the string.
- Surrounding markup - the string still wearing the url("...") or src="..." it came from. This one is nastiest: in an img tag the trailing alt="logo" is itself made of Base64 characters, so a decoder that just strips anything outside the alphabet swallows altlogo into the payload and returns a corrupted file, not an error.
The Base64 to image tab handles all four and reports which it fixed, so you also learn what was wrong. It identifies the format from the file's first bytes rather than the header, because a data URI claiming image/jpeg while carrying PNG bytes is common - and the bytes are what a browser goes by.
Doing it from the command line
Useful in a build script, but the two common base64 commands are different programs. GNU coreutils, on Linux, wraps at 76 columns unless you write base64 -w 0 icon.png. The BSD version on macOS accepts no bare filename at all and needs base64 -i icon.png; it does not wrap, and its wrapping flag is -b, not -w. A script that works on one but not the other has usually tripped over this.
For a whole data URI on Linux: printf 'data:image/png;base64,%s' "$(base64 -w 0 icon.png)". In Node, fs.readFileSync('icon.png').toString('base64').
Frequently asked questions
- How do I convert an image to Base64 without uploading it anywhere?
- Use a converter that runs in the browser rather than on a server. The whole operation is local by nature - the file is read with the browser's own FileReader and encoded in memory - so there is no technical reason for an image to leave your device to be Base64 encoded, and a tool that uploads it is doing so for its own convenience rather than yours. That matters more here than for most conversions, because the images people inline tend to be the ones they cannot hand to a third party: an unreleased logo, a scanned signature, a screenshot of an internal dashboard. If you would rather not use a tool at all, base64 -i icon.png on macOS or base64 -w 0 icon.png on Linux does the same job offline.
- Does converting an image to Base64 reduce its quality?
- No, and it does not reduce its size either - those are the two things people most often expect it to do. Base64 is an encoding, not a compression format or a re-render: it is a reversible way of writing the exact same bytes using only 64 text characters, and decoding it gives back a file that is identical to the original byte for byte. A JPEG encoded to Base64 and back is not re-compressed and loses nothing. If you want the file to be smaller, compress it before you encode it, because Base64 will faithfully add a third to whatever you give it.
- Why is my Base64 image not showing up in CSS or HTML?
- Four causes cover almost all of it. The media type is wrong or missing, so the browser is told it is receiving something it is not - data:image/png;base64, in front of JPEG bytes will often fail. The string contains line breaks, which survive fine in a CSS file but break an HTML attribute. The quoting collides: a percent-encoded SVG containing a double quote inside a url("...") ends the URL early, which is why attributes are switched to single quotes first. Or the payload is truncated, usually because it was copied from a terminal or editor that wrapped or elided it - a Base64 string whose length leaves a remainder of one when divided by four is definitely incomplete.