Image to Base64: encoding, decoding and data URIs
To convert an image to Base64, encode its raw bytes and add a prefix: base64 -w0 image.png on Linux, base64 -i image.png on macOS, or the PowerShell one-liner below on Windows. Put data:image/png;base64, in front of the output and you have a data URI: the file's bytes carried as plain text, ready to paste into an <img> tag, a stylesheet or a JSON payload. This page shows working examples, the honest pros and cons, and how to decode the string back into the original file.
What a data URI is made of
Every data URI has three parts. The data: scheme marks it as inline content. Then the MIME type and the encoding marker, image/png;base64. Then, after the comma, the file itself: every 3 bytes of the original rendered as 4 characters from the Base64 alphabet (letters, digits, + and /, padded with =).
Here is a complete, working example. The image is a real 8-by-8 pixel PNG, 74 bytes as a file and 100 characters once encoded:
<img width="8" height="8" alt="a small orange square"
src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAgAAAAICAIAAABLbSncAAAAEUlEQVR4nGP4EKWBFTEMLQkAVuRcge/othAAAAAASUVORK5CYII=">The same trick works in CSS:
.badge {
background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAgAAAAICAIAAABLbSncAAAAEUlEQVR4nGP4EKWBFTEMLQkAVuRcge/othAAAAAASUVORK5CYII=);
}No file request, no second round trip: the browser decodes the string back into bytes the moment it builds the page.
When inlining an image helps
- Small icons used everywhere. The 33 percent premium on a 300-byte icon is 100 extra bytes, and the request you skip costs more than that in connection overhead. This is the classic data URI win.
- Single-file artifacts. An HTML file with its icons inline travels as one self-contained document: one attachment, one archive, no broken references.
- Text-only channels. JSON APIs, database text columns and config formats accept strings, not bytes. A thumbnail as a Base64 string rides inside a JSON response without a file upload on the side.
Email is the exception: many clients strip data URIs for security reasons, so attach the image instead of embedding it.
When it hurts
- One third bigger, always. The 3-to-4 ratio is exact: a 100 KB file becomes about 133 KB of Base64. Verified here by encoding 3,000 random bytes into 4,000 characters.
- The cache entry disappears. A shared
logo.pngis downloaded once and reused by every page. The same bytes inlined ride inside each page, so they re-download whenever the page or stylesheet does, and updating the icon means re-shipping every document that embeds it. - CSS gets slow before HTML does. Inline images inside a stylesheet delay first paint, because the browser downloads all of the CSS before rendering anything it describes. A few dozen kilobytes of Base64 in your main stylesheet is noticeable.
- The markup turns into a wall of letters. Diffs, code review and the HTML source all get harder to read, and you lose
loading="lazy",srcsetand image tooling on the way.
Encode an image from the command line
A browser paste box holds text, not raw file bytes, so image files go through one line of command. Each of these prints the Base64 string to the terminal:
# Linux (GNU coreutils): -w0 keeps the output on one line,
# the default wraps every 76 characters
base64 -w0 image.png
# macOS (BSD base64): no -w flag, and it does not wrap by default
base64 -i image.png
# Windows PowerShell
[Convert]::ToBase64String([IO.File]::ReadAllBytes("image.png"))To produce the full data URI in one step on Linux or macOS:
echo "data:image/png;base64,$(base64 -w0 image.png)" > image.b64Decoding back is where Linux and macOS disagree, and it bites people who copy commands between them:
| Task | Linux (GNU) | macOS (BSD) |
|---|---|---|
| encode a file | base64 -w0 image.png | base64 -i image.png |
| decode to bytes | base64 -d image.b64 | base64 -D image.b64 |
On macOS the decode flag is a capital -D. Lowercase -d is a different flag there, so a Linux command pasted onto a Mac does not decode, and on many versions it quietly does something else instead. Check the output file before you trust it.
To prove a round trip worked, decode into a new file and compare bytes:
base64 -w0 image.png | base64 -d > copy.png
cmp image.png copy.png # no output means byte-identicalBase64 of text strings (not files) is a different job, and our Base64 tool does it live in your browser: nothing is uploaded, nothing stored.
Decode Base64 back into an image
The command-line decode from the table above rebuilds the exact original file, byte for byte. Redirect into a new filename; decoding in place over the source file destroys it, because the shell truncates the target before the decoder reads it.
For a quick visual check without the terminal, drop the data URI into a scratch HTML file and open it:
<!-- check.html -->
<img src="data:image/png;base64,PASTE-YOUR-STRING-HERE" alt="check">One caution with strings you did not create. Base64 is a transport format, not a safety check, so treat decoded bytes from strangers the way you treat any downloaded file. Keep untrusted images inside <img> tags, where they render but cannot execute anything. SVG is the sharp edge: opened as a document rather than embedded, an SVG can carry scripts, which is exactly why current browsers refuse to navigate directly to a data: URL. Decode with your terminal, view inside an img, and never run what you have not inspected.
The limits worth knowing
- Size caps. The old limit lives in history books: IE8 accepted data URIs up to 32,768 bytes, which made inline images a non-starter. Current Chrome, Firefox, Safari and Edge render multi-megabyte data URIs inside a document, so the real cap today is your page weight, not the browser.
- MIME correctness. The prefix must match the bytes:
image/png,image/jpeg,image/webp,image/gif. Browsers sniff around small mismatches, but APIs and strict parsers do not, which turns a wrong MIME type into a works-on-my-machine bug.file image.pngprints the true type on Linux and macOS. - The SVG special case. SVG is text, so it has a second option: a UTF-8 data URI like
data:image/svg+xml,...<svg>...with the special characters percent-encoded. It stays smaller and human-readable, but#,%and quotes must be escaped. The Base64 form costs the 33 percent and sidesteps the escaping. Either way, scripts inside an SVG never run once it sits in an<img>.
Frequently Asked Questions
How do I convert an image to Base64?
Run one command on the file: base64 -w0 image.png on Linux, base64 -i image.png on macOS, or [Convert]::
Why is Base64 bigger than the original file?
Base64 encodes every 3 bytes of input as 4 characters, and 4 divided by 3 is about 1.33. A 100 KB image becomes roughly 133 KB of Base64, plus a couple dozen characters for the data: prefix. The size premium is the price of carrying binary data through formats that are built for text.
Can I put Base64 images in CSS?
Yes: a background-image with url(data:image/png;base64,...) works in every current browser. Keep it to small icons. A large image inside a stylesheet makes the stylesheet heavier and delays first paint, because browsers download all of the CSS before rendering anything it describes, and the image also loses its own cache entry. IE8 capped CSS data URIs at 32 KB, but that limit is history.