Convert a Data URL to an Image in JavaScript
Turn a data URL into a usable HTMLImageElement, handle errors, Blob URLs, CSP, large files, and display or process the image safely.

A browser data URL already contains an image in URL form. To turn it into an image object, create an HTMLImageElement, assign the complete data URL to src, and wait for its load or error event.
function imageFromDataUrl(dataUrl) {
return new Promise((resolve, reject) => {
const img = new Image();
img.onload = () => resolve(img);
img.onerror = () => reject(new Error("Could not load image data URL"));
img.src = dataUrl;
});
}
const img = await imageFromDataUrl(dataUrl);
document.querySelector("#preview").append(img);
console.log(img.naturalWidth, img.naturalHeight);
new Image() and document.createElement("img") create the same kind of element. Keep the complete string, including its data: prefix and metadata. Setting src starts an asynchronous decode; it does not make pixels available synchronously.
1. Understand the data URL format
The general syntax is data:[<media-type>][;base64],<data>. A typical PNG looks like this:
data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAA...
The comma separates metadata from the payload. The media type tells the browser what kind of content to decode. If you omit it, the data URL defaults to text/plain;charset=US-ASCII, which is not an image type. The ;base64 marker is required only when the payload is Base64 encoded. Data URLs can also contain percent-encoded bytes.
Do not strip the prefix, decode and re-encode an already valid URL, or pass only the Base64 characters to src. Those transformations commonly produce an invalid image.
2. Load a data URL with a promise
A promise wrapper gives calling code a clear success and failure path. Attach handlers before assigning src, because a cached or tiny resource can complete quickly.

function imageFromDataUrl(dataUrl, { timeout = 15000 } = {}) {
return new Promise((resolve, reject) => {
if (typeof dataUrl !== "string" || !dataUrl.startsWith("data:")) {
reject(new TypeError("Expected a data URL string"));
return;
}
const img = new Image();
let timer;
const finish = (callback) => {
clearTimeout(timer);
img.onload = null;
img.onerror = null;
callback();
};
img.onload = () => finish(() => {
if (img.naturalWidth === 0 || img.naturalHeight === 0) {
reject(new Error("Image decoded with no intrinsic dimensions"));
} else {
resolve(img);
}
});
img.onerror = () => finish(() => {
reject(new Error("The data URL is not a supported, decodable image"));
});
timer = setTimeout(() => finish(() => {
reject(new Error("Timed out while decoding the data URL"));
}), timeout);
img.src = dataUrl;
});
}
async function showImage(dataUrl) {
const img = await imageFromDataUrl(dataUrl);
img.alt = "Uploaded image preview";
img.className = "preview-image";
document.querySelector("#preview").replaceChildren(img);
}
Use naturalWidth and naturalHeight for the decoded image’s intrinsic dimensions. They are different from the CSS width and height that control display size. The browser reference for HTMLImageElement documents these properties and the load/error lifecycle: MDN HTMLImageElement.
Display an existing data URL directly
If you only need to show the image, no conversion function is necessary:
<img id="preview" alt="Preview">
<script>
const preview = document.querySelector("#preview");
preview.addEventListener("load", () => {
console.log(preview.naturalWidth, preview.naturalHeight);
});
preview.addEventListener("error", () => {
console.error("Image could not be decoded");
});
preview.src = dataUrl;
</script>
Set an appropriate alt value when the image conveys information. If it is decorative, use an empty value such as alt="".
3. Validate before you decode
Validation should protect your application from malformed input and unreasonable resource use. It cannot prove that arbitrary bytes are a valid image; the decoder’s load event remains the final check.
function inspectDataUrl(value) {
if (typeof value !== "string" || !value.startsWith("data:")) {
throw new TypeError("Value must start with data:");
}
const comma = value.indexOf(",");
if (comma < 0) throw new Error("Data URL has no payload separator");
const metadata = value.slice(5, comma).toLowerCase();
const payload = value.slice(comma + 1);
const isBase64 = metadata.split(";").includes("base64");
const mediaType = metadata.split(";")[0] || "text/plain;charset=us-ascii";
if (!mediaType.startsWith("image/")) {
throw new Error(`Expected an image media type, received ${mediaType}`);
}
if (isBase64 && !/^[a-z0-9+/\s]*={0,2}$/i.test(payload)) {
throw new Error("Payload is not valid Base64 text");
}
return { mediaType, isBase64, payloadLength: payload.length };
}
Check the expected MIME types for your feature, for example image/png, image/jpeg and image/webp. Do not trust a client supplied MIME type for security decisions. If uploads are untrusted, enforce a byte limit, decode in a constrained workflow, and avoid treating the string as a navigation target.
4. Convert a Blob to an image with an object URL
A Blob is binary data, not a data URL. In a browser, expose it to an image with URL.createObjectURL(blob):
async function imageFromBlob(blob) {
if (!(blob instanceof Blob)) throw new TypeError("Expected a Blob");
const objectUrl = URL.createObjectURL(blob);
try {
const img = await imageFromDataUrl(objectUrl);
return { img, objectUrl };
} catch (error) {
URL.revokeObjectURL(objectUrl);
throw error;
}
}
const file = document.querySelector("input[type=file]").files[0];
const { img, objectUrl } = await imageFromBlob(file);
document.querySelector("#preview").replaceChildren(img);
// Call this when the preview is removed or replaced.
function disposePreview() {
URL.revokeObjectURL(objectUrl);
img.remove();
}
For a Blob, revoke the object URL when the image is no longer accessible. Revoking immediately after load can break later interaction such as opening or saving the preview. See MDN URL.createObjectURL().
Choose the representation you already have:
| Input | Use | Cleanup |
|---|---|---|
| Data URL string | Assign the complete string to img.src |
No object URL to revoke |
| Blob or File | Call URL.createObjectURL(blob) |
Revoke after the preview is gone |
5. Draw the decoded image on a canvas
Wait for decoding before drawing. Set the canvas’s backing dimensions from intrinsic dimensions so the result is not accidentally blurry.
const img = await imageFromDataUrl(dataUrl);
const canvas = document.querySelector("canvas");
canvas.width = img.naturalWidth;
canvas.height = img.naturalHeight;
const context = canvas.getContext("2d");
context.drawImage(img, 0, 0);
const pngDataUrl = canvas.toDataURL("image/png");
If you draw cross-origin network images, the canvas can become tainted unless the server and request use compatible CORS headers. A data URL is embedded in the page, so it does not require a network CORS response, but your page’s security policy still applies.
6. CSP, browser limits and security
A Content Security Policy controls allowed image sources through img-src. A valid data URL can fail in production when the deployed response policy does not allow the data: scheme. Inspect the actual response headers and adjust the policy only as your security model permits; consult MDN img-src.
Data URLs create embedded content and can be very large. MDN reports current practical maximums of 512 MB in Chromium and Firefox and 2048 MB in Safari/WebKit, while noting that browsers are not required to support a universal limit. These are ceilings, not good application targets. Large strings consume memory, increase copying and can slow parsing. Prefer a Blob and object URL for large user files, or store the file outside the page and load it through a controlled URL.
Do not let arbitrary input become a top-level navigation URL. Modern browsers restrict top-level navigation to data URLs, and the format has a history of security problems in that context. Keep image data in an image element, canvas or controlled processing path.
7. Complete file-input example
<input id="file" type="file" accept="image/png,image/jpeg,image/webp">
<div id="status" role="status"></div>
<div id="preview"></div>
<script>
const input = document.querySelector("#file");
const status = document.querySelector("#status");
const preview = document.querySelector("#preview");
let currentUrl;
input.addEventListener("change", async () => {
const file = input.files[0];
if (!file) return;
if (!file.type.startsWith("image/")) {
status.textContent = "Choose an image file.";
return;
}
if (file.size > 10 * 1024 * 1024) {
status.textContent = "Choose an image smaller than 10 MB.";
return;
}
if (currentUrl) URL.revokeObjectURL(currentUrl);
currentUrl = URL.createObjectURL(file);
const img = new Image();
img.alt = "Selected image preview";
img.onload = () => {
status.textContent = `${img.naturalWidth} × ${img.naturalHeight}`;
preview.replaceChildren(img);
};
img.onerror = () => {
status.textContent = "The file could not be decoded as an image.";
URL.revokeObjectURL(currentUrl);
currentUrl = undefined;
};
img.src = currentUrl;
});
</script>
8. Troubleshooting
| Symptom | Likely cause | Fix |
|---|---|---|
error fires immediately |
Missing data:, comma, media type or valid payload |
Inspect the prefix and run structural validation; confirm the bytes are a supported image. |
| Image shows as broken icon | Base64 marker does not match the payload, or the string was truncated | Use ;base64 only for Base64 data and preserve padding; check transport and storage limits. |
| Works locally but not after deployment | CSP blocks the source | Inspect Content-Security-Policy and the img-src directive. |
| Dimensions are zero | Code reads dimensions before decoding completes | Read naturalWidth and naturalHeight inside load or after the promise resolves. |
| Blob preview disappears later | Object URL was revoked too soon | Revoke it when replacing or removing the image, not immediately after load. |
| Memory grows after many previews | Old object URLs, image elements or data strings remain referenced | Revoke old URLs, replace detached nodes and release large strings. |
| Canvas export throws a security error | A network image tainted the canvas | Use same-origin assets or configure CORS correctly before drawing. |
9. Performance, reliability and cost notes
- Assigning a data URL avoids a network round trip, but Base64 increases payload size and large JavaScript strings can create memory pressure.
- Do not repeatedly decode the same string. Cache the resulting element or Blob when the image is reused.
- Use a Blob URL for large files and clean it up on component unmount, route change or replacement.
- Set application limits for input length and file size. Browser maximums vary and are not a guarantee that an image of that size is practical.
- Handle both success and failure. A syntactically valid URL can still contain corrupt or unsupported bytes.
- No universal speed advantage exists between a data URL and an object URL; choose based on the representation, lifetime and size you need.
10. Node.js and server-side JavaScript
Image(), HTMLImageElement and the DOM are browser APIs. Standard Node.js does not provide them, so the browser code above is not a server-side decoding recipe. In Node, keep the data URL as a string, parse its metadata and decode the payload with an image library selected for your server’s requirements. If the next step is simply obtaining a screenshot, a screenshot API avoids installing and operating a browser.
11. Or skip the browser setup
ScreenshotNeo returns a PNG, JPEG, WebP or PDF from one GET request. It removes cookie and consent banners, newsletter popups and chat widgets before capture. Bot checks, blank pages, timeouts, failed loads and cache hits are not billed, and response headers identify the page verdict and billing result. Its MCP server provides take_screenshot, get_page_info and capture_pdf for Claude, Cursor and other MCP clients.

See the ScreenshotNeo API documentation for all options. A minimal JavaScript request is:
const q = new URLSearchParams({
access_key: 'YOUR_API_KEY',
url: 'https://stripe.com'
});
const res = await fetch(`https://api.screenshotneo.com/v1/shot?${q}`);
if (!res.ok) throw new Error(`Screenshot failed: ${res.status}`);
const imageBytes = await res.arrayBuffer();
The equivalent cURL and Python calls are:
curl -G "https://api.screenshotneo.com/v1/shot" \
-d access_key=YOUR_API_KEY \
--data-urlencode url=https://stripe.com \
-o shot.webp
import requests
r = requests.get(
"https://api.screenshotneo.com/v1/shot",
params={"access_key": "YOUR_API_KEY", "url": "https://stripe.com"},
timeout=90,
)
r.raise_for_status()
open("shot.webp", "wb").write(r.content)
ScreenshotNeo supports full-page capture with lazy images loaded, CSS element capture, dark mode, device presets, custom viewports, retina scale, PDF settings, custom CSS and JavaScript, clicks, waits, blocking rules, headers, cookies, user agents, authorization, timezone, geolocation, transparent backgrounds, resizing, configurable caching, signed links, asynchronous webhooks, bulk capture and a usage API. One thousand screenshots per month are free with no card; paid plans start at $5 for 3,000.
Create your free ScreenshotNeo account and start with 1,000 screenshots each month at no charge.
12. FAQ
Can I use a data URL directly in CSS?
Yes. A data URL can be assigned to background-image or used in other URL-valued CSS properties, subject to CSP. Use an image element when you need load events or intrinsic dimensions.
Do I need to decode Base64 manually?
No. The browser image decoder understands a valid data:image/...;base64,... URL. Manual decoding is useful only when you need the raw bytes for another API.
Why does img.src = dataUrl not return an image?
The assignment starts loading; it does not return a promise. Resolve your own promise from load and reject it from error.
Should I convert every data URL to a Blob?
No. Keep an existing data URL when it is small and already in the required form. Use a Blob URL when you hold binary data or need a better lifecycle for a large preview.
Can a data URL be trusted because it starts with data:?
No. The prefix identifies a scheme, not safe or valid image bytes. Validate expected types and sizes, then rely on the decoder result.


