[GUIDE] Understanding Data URIs: data:image/png;base64,... Explained
A practical guide to the data: URI scheme — what every part of data:image/png;base64,iVBORw0KGgo... actually means, when inlining is the right call, and when it bites you back.
// WHAT A data: URI ACTUALLY IS
A data: URI is a URI scheme — like https: or file: — that embeds the resource itself directly into the URI string instead of pointing to a remote location. Defined by RFC 2397, it lets you treat a small blob of binary or text data as if it were a URL.
The most common form you'll see in the wild is for images: data:image/png;base64,iVBORw0KGgoAAAANSUhEUg.... The browser sees this URL, reads the MIME type, decodes the Base64 payload back into bytes, and renders the image — without ever making an HTTP request.
// THE SYNTAX, PIECE BY PIECE
The full syntax is data:[<mediatype>][;base64],<data>. Three optional parts and one required separator (the comma). Let's break a real example down:
Take data:image/png;base64,iVBORw0KGgoAAAANSUhEUg... and read it left to right:
• data: — the scheme. Tells the parser "the resource is in the URL itself, no fetch needed."
• image/png — the MIME type. Tells the renderer how to interpret the payload. Defaults to text/plain;charset=US-ASCII if omitted.
• ;base64 — encoding flag. Says the payload is Base64 (RFC 4648). Without it, the payload is interpreted as URL-encoded text.
• , — the required separator between metadata and payload.
• iVBORw0KGgo... — the actual Base64-encoded image bytes.
// Anatomy diagram
// data:image/png;base64,iVBORw0KGgoAAAANSUhEUg...
// │ │ │ │
// │ │ │ └── Base64 payload (the actual image bytes)
// │ │ └────────── encoding flag (always 'base64' for binary)
// │ └──────────────────── MIME type (optional, defaults to text/plain)
// └───────────────────────── scheme literal (always 'data:')
// WHY data: URIs EXIST
Three reasons people reach for data URIs instead of regular image URLs:
1. Eliminate an HTTP request. Every <img src="..."> on a page costs a network round-trip. For a tiny icon or sprite, that round-trip can take longer than transferring the bytes themselves. Inlining the image as a data URI bundles it with the HTML/CSS, removing the request entirely. This used to matter a lot in the HTTP/1.1 era; with HTTP/2 multiplexing it matters less, but for above-the-fold critical icons it still helps.
2. Make the asset self-contained. An HTML email needs to render in clients that block remote images by default (Gmail, Outlook). A static report you email to a client needs to display the company logo even when offline. A bookmarklet needs an icon that survives copy-paste. A documentation snippet needs a screenshot that travels with the markdown. In all of these, the image has to live inside the document — and a data URI is how you do that.
3. Programmatic generation. Code generates an image at runtime — a QR code, a chart, a signature pad — and you need to display it without first uploading it to a server and getting back a URL. canvas.toDataURL('image/png') hands you a data URI directly; assigning it to img.src is the simplest possible workflow.
// EVERY MIME TYPE YOU'LL ACTUALLY SEE
The MIME-type slot accepts any media type, but in practice you'll see a small handful for images:
• image/png — most common. Lossless, supports transparency. Base64 starts with iVBORw0KGgo.
• image/jpeg — photos and screenshots. Lossy. Base64 starts with /9j/.
• image/gif — legacy and animated images. Base64 starts with R0lGOD.
• image/webp — modern, smaller files. Base64 starts with UklGR.
• image/svg+xml — vector graphics. SVG is text, so you can either Base64-encode it (with ;base64) or URL-encode it (without). URL-encoded SVG is usually smaller.
• image/x-icon or image/vnd.microsoft.icon — favicons. Base64 starts with AAABAA.
<!-- All five types in a single HTML document -->
<img src="data:image/png;base64,iVBORw0KGgo..." alt="PNG inline">
<img src="data:image/jpeg;base64,/9j/4AAQSkZJRg..." alt="JPEG inline">
<img src="data:image/gif;base64,R0lGODlhAQABAA..." alt="GIF inline">
<img src="data:image/webp;base64,UklGRiIAAABXRU..." alt="WebP inline">
<img src="data:image/svg+xml;base64,PHN2ZyB4bWxu..." alt="SVG inline">
// USING data: URIs IN HTML, CSS, AND JAVASCRIPT
Anywhere a URL is accepted, a data URI works the same way.
<!-- HTML <img> tag -->
<img src="data:image/png;base64,iVBORw0KGgo..." width="32" height="32" alt="icon">
<!-- HTML <link> for favicon -->
<link rel="icon" href="data:image/x-icon;base64,AAABAAEAEBA...">
/* CSS background-image */
.button-icon {
background-image: url('data:image/svg+xml;base64,PHN2ZyB4bWxu...');
background-size: 16px 16px;
}
/* CSS @font-face — yes, fonts work too */
@font-face {
font-family: 'InlineFont';
src: url('data:font/woff2;base64,d09GMgAB...') format('woff2');
}
// JavaScript — assign directly to img.src
const img = new Image();
img.src = 'data:image/png;base64,iVBORw0KGgo...';
document.body.appendChild(img);
// JavaScript — fetch a data URI like any other URL
const response = await fetch('data:application/json;base64,eyJrZXkiOiJ2YWx1ZSJ9');
const data = await response.json(); // { key: 'value' }
// CANVAS, FILE, AND BLOB → data: URI
Three browser APIs hand you a data URI without you doing the Base64 yourself:
// 1. Canvas → data URI (instant)
const canvas = document.querySelector('canvas');
const pngDataURI = canvas.toDataURL('image/png');
const jpegDataURI = canvas.toDataURL('image/jpeg', 0.85); // quality 0–1
// pngDataURI === 'data:image/png;base64,iVBORw0KGgo...'
// 2. <input type="file"> → data URI via FileReader
const file = document.querySelector('input[type=file]').files[0];
const reader = new FileReader();
reader.onload = () => {
// reader.result is the data URI
document.querySelector('img').src = reader.result;
};
reader.readAsDataURL(file);
// 3. Blob → data URI (manual, but rarely needed — use URL.createObjectURL instead)
const blob = new Blob([bytes], { type: 'image/png' });
const reader2 = new FileReader();
reader2.onload = () => console.log(reader2.result);
reader2.readAsDataURL(blob);
// Or, for in-page display, prefer:
const objectURL = URL.createObjectURL(blob); // 'blob:https://...' — much shorter, no Base64
// THE 33% SIZE PENALTY (AND WHEN IT MATTERS)
Base64 turns 3 bytes of binary into 4 ASCII characters, so the encoded payload is exactly 4⌈n/3⌉ bytes — roughly 33% larger than the original. A 12 KB PNG becomes a ~16 KB data URI, plus a few bytes of overhead for the data:image/png;base64, prefix.
When does this matter?
• Tiny assets (under 4 KB): the 33% bloat is dwarfed by the saved HTTP round-trip. Inline freely.
• Medium assets (4–50 KB): case-by-case. With HTTP/2 + caching, a separate request is usually faster than re-downloading the inlined version every page load.
• Large assets (50 KB+): almost never inline. The data URI bloats every cached HTML page, every email, every JSON payload that contains it. Use a separate URL.
There is no caching for data URIs — the browser cannot cache an image whose bytes are baked into the HTML. If the same image appears on multiple pages, every page pays the full cost. A regular <img src="/logo.png"> with HTTP cache headers is downloaded once and reused everywhere.
// BROWSER LIMITS YOU'LL HIT
Browsers do not impose a single hard limit on data URIs, but real-world ceilings exist:
• Chrome / Edge / Firefox: data URIs in <img src> work up to several megabytes. Beyond ~32 MB you'll hit per-tab memory pressure.
• Safari: historically capped data URIs in <a href> (download links) at around 2 MB. For <img>, larger sizes work but rendering slows.
• Internet Explorer 8: 32 KB max for data URIs in CSS and HTML. (IE9+ removed the limit but it was never as fast as modern browsers.)
• HTTP request line: data URIs in <form action> or query strings are bounded by the URL length the browser accepts (~2 KB for IE, 8 KB+ for others).
If you find yourself approaching any of these limits, the asset is too big to inline. Switch to a regular URL or to URL.createObjectURL(blob), which gives you a short blob: URL backed by a blob you can keep in memory.
// SECURITY CONCERNS
Data URIs are network-free, which is convenient — but they bypass several web-security mechanisms that assume content comes from a server.
Same-origin policy. A data: URI is technically considered opaque origin (in modern Chrome/Firefox), so JavaScript loaded from a data URI cannot access the parent document's cookies, localStorage, or DOM in unrestricted ways. This is good for sandboxing untrusted content, but it also means tracking, analytics, and CSRF protections that rely on origin checks may misbehave.
XSS via data:text/html. A data:text/html,<script>...</script> URL executed by a user becomes a fully privileged page. Modern browsers block top-level navigation to data:text/html for this reason (Firefox since 2018, Chrome since 2020). Don't pass user-supplied URLs through any redirect that allows the data: scheme.
Content Security Policy (CSP). By default, strict CSPs block data URIs in img-src, style-src, etc. To allow them, you must explicitly include data: in the directive (e.g., img-src 'self' data:). Be deliberate — once you allow data URIs, you also accept any image (or font) the page chooses to inline.
Logging and PII. A data URI is part of the URL. Any logger or analytics tool that captures URLs (server access logs, browser history, error trackers) will capture the entire payload. If your data URI contains a user's avatar, photo, or screenshot, it ends up in your logs. Don't put PII in URLs you don't control.
// data: URI vs. blob: URL — WHICH SHOULD YOU USE?
Both let you display in-memory binary data without uploading it. The choice depends on where the data needs to live.
Use data: URI when the data needs to be portable — embedded in HTML, sent in JSON, saved to a database, copy-pasted into an email. The data is the URL.
Use blob: URL when the data is local and short-lived — a preview in a single-page app, a download trigger, an image that disappears when the page closes. URL.createObjectURL(blob) hands you a short identifier (blob:https://example.com/12345-abcde) that the browser maps back to the in-memory blob. Far smaller than a data URI, and you can revoke it with URL.revokeObjectURL(url) when done.
Rule of thumb: if the URL needs to leave the current page, use data:. If it stays inside the page, use blob:.
// COMMON DEBUGGING ISSUES
Truncated string from DevTools. When you copy a long data URI from the Network tab or Elements panel, browsers often truncate the value with an ellipsis. The displayed text is not the full URL. To get the full string, open the source file (CSS, HTML, JSON) directly, or use document.querySelector('img').src in the console — that returns the complete URL.
Wrong MIME type. A PNG payload labeled data:image/jpeg;base64,... will still decode at the byte level — Base64 doesn't care about MIME — but some viewers reject the mismatch. The actual format is determined by the magic bytes (iVBORw0KGgo for PNG), so when in doubt, paste the payload into our Base64 to Image decoder; it auto-detects from the bytes.
Missing comma. The format is data:image/png;base64,iVBORw..., not data:image/png;base64;iVBORw.... A semicolon or missing separator makes the entire URL invalid.
URL-safe Base64 in a data: URI. Standard data: URIs use the standard Base64 alphabet (with + and /). If your payload was encoded with the URL-safe variant (RFC 4648 §5, with - and _), you must convert it back before embedding — see our Base64 URL-safe vs Standard guide.
Whitespace inside the payload. Some pretty-printers wrap long Base64 strings at 76 columns with newlines. Most browsers tolerate this in <img src>, but a few (especially older WebViews) do not. Strip whitespace before embedding: str.replace(/\s+/g, '').
// WHEN NOT TO USE data: URIs
• Above 50 KB. The cache-bust + download cost outweighs the saved request.
• Repeated images. Anything used on more than one page should be a regular URL with cache headers.
• Public-facing analytics. Tracking pixels, beacons, and pixel-based attribution will not work because there's no HTTP request to log.
• User-uploaded content displayed in an SPA. Use URL.createObjectURL(blob) instead — it avoids the Base64 work entirely and is roughly 4× smaller in memory.
• Inside a CSP-strict environment that doesn't allow data:. Don't add data: to your CSP just to use a data URI; ask first whether you really need it.
// 30-SECOND CHEAT SHEET
Format: data:[mime-type][;base64],[payload]
Most common form: data:image/png;base64,iVBORw0KGgo...
Decode it back to an image: paste into our Base64 to Image converter.
Encode an image to a data URI: use our Image to Base64 converter — it produces the full data: URI ready to paste.
Inline budget: < 4 KB always inline, 4–50 KB it depends, > 50 KB use a regular URL.
Browser API: canvas.toDataURL(), FileReader.readAsDataURL(), or just construct the string yourself: 'data:image/png;base64,' + btoa(binaryString).
Related reads: Base64 image embedding · Previewing Base64 images in the browser · Base64 vs Base64URL vs Base32