The Tool Desk
Outbyte PC Repair FREERepair Windows errors before they cause bigger problemsFix Now →Outbyte Driver Updater FREEFix the driver behind crashes, sound loss and screen glitchesFind Drivers →Use canvas.toBlob() to encode the canvas bitmap as a PNG, create a temporary object URL, and click a download link. This asynchronous approach avoids keeping a second, large data-URL string in memory. For a small image that must be embedded inline, canvas.toDataURL('image/png') is a convenient alternative.
The practical PNG download method
This function works with any existing <canvas> element. It creates a PNG Blob, turns that blob into a temporary URL, starts a browser download, and releases the URL immediately after the browser has had an opportunity to use it.
function downloadCanvasAsPng(canvas, filename = "canvas.png") {
canvas.toBlob((blob) => {
if (!blob) {
throw new Error("Canvas could not be encoded.");
}
const url = URL.createObjectURL(blob);
const link = document.createElement("a");
link.href = url;
link.download = filename;
document.body.appendChild(link);
link.click();
link.remove();
// Revoke after the browser has had a chance to start the download.
setTimeout(() => URL.revokeObjectURL(url), 0);
}, "image/png");
}
const canvas = document.querySelector("#drawing");
downloadCanvasAsPng(canvas, "my-drawing.png");
The second argument, "image/png", explicitly requests PNG. PNG is also the default format when no type is supplied, and browsers fall back to PNG when a requested format is unsupported. The callback runs later, so code that needs the blob must stay inside the callback or be converted to a promise.
A complete working page
The canvas element’s width and height attributes define the bitmap that is exported. CSS changes only how that bitmap is displayed. In this example the saved file is 800 by 450 pixels even if the canvas is styled to appear smaller or larger.
Free tools Windows power users keep installed
One-click scans. No signup required.
#1 Best Overall
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Canvas PNG export</title>
<style>
canvas { width: 400px; border: 1px solid #777; }
</style>
</head>
<body>
<canvas id="drawing" width="800" height="450"></canvas>
<button id="save" type="button">Download PNG</button>
<script>
const canvas = document.querySelector("#drawing");
const context = canvas.getContext("2d");
context.fillStyle = "#17324d";
context.fillRect(0, 0, canvas.width, canvas.height);
context.fillStyle = "#ffffff";
context.font = "48px sans-serif";
context.fillText("Export me", 70, 180);
context.fillStyle = "#58c4dc";
context.fillRect(70, 230, 300, 22);
function downloadCanvasAsPng(canvas, filename = "canvas.png") {
canvas.toBlob((blob) => {
if (!blob) throw new Error("Canvas could not be encoded.");
const url = URL.createObjectURL(blob);
const link = document.createElement("a");
link.href = url;
link.download = filename;
document.body.appendChild(link);
link.click();
link.remove();
setTimeout(() => URL.revokeObjectURL(url), 0);
}, "image/png");
}
document.querySelector("#save").addEventListener("click", () => {
downloadCanvasAsPng(canvas, "export-me.png");
});
</script>
</body>
</html>
Open the file in a browser, draw or modify the canvas, and press Download PNG. The downloaded file contains the current bitmap, including transparency, pixels, and drawing operations that have already been rendered. It does not contain the canvas’s JavaScript instructions or CSS layout.
toBlob() versus toDataURL()
| Aspect | toBlob() |
toDataURL() |
|---|---|---|
| Result | Asynchronous Blob supplied to a callback |
Synchronous data URL string |
| Large images | Preferred; avoids duplicating the complete image in a JavaScript string | Can consume substantial memory and create a very long URL |
| Downloading | Create an object URL with URL.createObjectURL(), then use a download link |
Assign the data URL directly to a link’s href |
| Inline use | Convert or upload the blob as needed | Convenient when an API specifically requires a data URL |
| Security rule | Both require an origin-clean canvas; CORS restrictions apply equally | |
When a data URL is appropriate
For a small icon or a quick preview, this is enough:
const pngDataUrl = canvas.toDataURL("image/png");
const link = document.createElement("a");
link.href = pngDataUrl;
link.download = "small-canvas.png";
link.click();
toDataURL() returns the complete encoded image as a string before the next statement runs. A large canvas therefore creates a large in-memory representation in addition to the bitmap. Very long data URLs can also run into URL-length limits in other systems. Use toBlob() for normal downloads, uploads, or high-resolution artwork.
Why “Tainted canvases may not be exported” appears
A canvas becomes tainted when you draw an image or other pixel source from another origin without the required CORS permission. The browser may display that image, but it prevents scripts from reading the pixels. Calling getImageData(), toBlob(), or toDataURL() then raises a SecurityError (or causes encoding to fail).
Rank #2
Load remote images with CORS enabled
Set the image’s CORS mode before assigning src, then draw it only after it has loaded:
const image = new Image();
image.crossOrigin = "anonymous"; // Must be set before src
image.onload = () => {
const canvas = document.querySelector("#drawing");
canvas.width = image.naturalWidth;
canvas.height = image.naturalHeight;
canvas.getContext("2d").drawImage(image, 0, 0);
downloadCanvasAsPng(canvas, "remote-image.png");
};
image.onerror = () => {
console.error("The image could not be loaded with CORS permission.");
};
image.src = "https://images.example.com/photo.jpg";
The image server must return an appropriate Access-Control-Allow-Origin response for your page’s origin (or a permitted wildcard where suitable). The crossOrigin property only requests a CORS-enabled fetch; it cannot override the server’s policy.
What to do when you do not control the image server
- Ask the owner to enable CORS for the origin running your application.
- Proxy the image through a server you control, adding the correct CORS response there and enforcing authorization and size limits.
- Use an image hosted on the same origin as your application.
- Do not rely on disabling browser security in production; that only changes one local browser and exposes the user to risk.
Once a non-CORS image has been drawn, clearing the canvas does not reliably restore its origin-clean state. Create a fresh canvas and redraw only approved sources.
Dimensions, transparency, and format checks
Pixel dimensions come from the bitmap
Set canvas.width and canvas.height before drawing. A canvas declared as width="1600" height="900" exports 1600 by 900 pixels even if CSS displays it at 800 by 450. Conversely, changing only CSS produces a visually scaled image but does not add pixels to the saved PNG. Resizing the bitmap clears its contents, so set dimensions before rendering or redraw afterward.
Do these 3 things before closing this tab:
1Clear out junk files and repair common Windows errors2Fix the driver behind crashes, sound loss and screen glitches3Repair Windows errors before they cause bigger problemsRank #3
Transparency is preserved
PNG supports an alpha channel. If you need transparent areas, do not paint an opaque background before exporting. A canvas that has been filled with white, for example, will contain white pixels rather than transparent pixels.
Verify the returned type
If you request a format other than PNG, inspect blob.type to confirm what the browser produced. Unsupported formats fall back to PNG:
canvas.toBlob((blob) => {
if (!blob) return;
console.log(blob.type); // For example: image/png
}, "image/webp");
Encoded formats that carry resolution metadata use 96 dpi in the browser APIs. That metadata does not change the actual pixel width or height.
Handle extreme sizes
Canvas limits vary by browser, device, and available memory. A zero-sized canvas or one beyond the implementation’s maximum may produce an unusable result or a null blob. Keep dimensions within what the target devices can allocate, and test the largest artwork your application permits rather than assuming one universal maximum.
Recommended Free Tools
Rank #4
Reusable patterns for previews and uploads
Keep an object URL for a preview
Do not revoke an object URL while an image is still using it. Revoke it when replacing the preview or when the user closes it:
let previewUrl;
function showCanvasPreview(canvas, imageElement) {
canvas.toBlob((blob) => {
if (!blob) return;
if (previewUrl) URL.revokeObjectURL(previewUrl);
previewUrl = URL.createObjectURL(blob);
imageElement.src = previewUrl;
}, "image/png");
}
function disposePreview() {
if (previewUrl) {
URL.revokeObjectURL(previewUrl);
previewUrl = undefined;
}
}
Upload the PNG without converting it to text
canvas.toBlob(async (blob) => {
if (!blob) throw new Error("PNG encoding failed");
const form = new FormData();
form.append("image", blob, "canvas.png");
const response = await fetch("/api/images", {
method: "POST",
body: form
});
if (!response.ok) throw new Error(`Upload failed: ${response.status}`);
}, "image/png");
Troubleshooting checklist
- The click does nothing: start the export from a user gesture such as a button click, append the link to the document before clicking it, and verify that the browser allows downloads.
- The callback receives
null: check for a zero-sized or oversized canvas, memory pressure, and encoding errors. Reduce dimensions and try again. - A
SecurityErroris thrown: inspect every image, video frame, SVG, or other external source drawn before export. SetcrossOriginbeforesrcand confirm the server’s CORS header. - The PNG is blurry: increase the canvas bitmap dimensions or render for the device pixel ratio; changing CSS width alone does not increase exported resolution.
- The image has the wrong size: inspect
canvas.widthandcanvas.height, not the computed CSS dimensions. - The background is unexpectedly opaque: look for a prior
fillRect()or compositing operation that painted the transparent area. - The downloaded file is PNG despite another requested type: check
blob.type; the browser uses PNG when the requested encoder is unsupported. - The preview later disappears: the object URL was revoked too early. Keep it until the image is replaced or the preview is disposed.
- Export works locally but not after deployment: production origins, image hosts, redirects, and CDN headers may differ. Check the final image response in browser developer tools.
Performance and reliability considerations
Encoding is asynchronous with toBlob(), so the browser can schedule the work without forcing your next JavaScript statement to wait for a complete base64-like string. For interactive editors, disable the export button while an encode is in progress, show progress or a busy state for very large canvases, and avoid exporting on every pointer-move event. Reuse a single download or preview flow and revoke object URLs that are no longer referenced.
For deterministic output, finish all drawing operations before calling the encoder. If fonts, images, or video frames are still loading, wait for them first. Keep the original bitmap dimensions in application state so a later CSS resize cannot silently alter expectations. A failed export should be reported to the user instead of creating an empty file.
Independent reader supportYour contribution helps us test, update, and keep practical guides available for everyone.Or skip the browser setup
If your real goal is a screenshot of a published page that contains a canvas, rather than exporting pixels from an in-memory canvas in your own application, ScreenshotNeo can capture the URL through one API request. It is a website screenshot API and MCP server. Before capture it accepts the cookie or consent banner like a visitor and removes more than 60 known consent platforms, newsletter popups, and chat widgets; each cleanup step can be disabled. Bot checks, CAPTCHAs, blank pages, timeouts, failed loads, and cache hits are not billed, and the response reports the page verdict and billing status in X-Page-Verdict and X-Billed headers.
Quick wins for a faster PC:
Fix the driver behind crashes, sound loss and screen glitchesFind Drivers →Repair Windows errors before they cause bigger problemsFix Now →Use the API documentation at https://screenshotneo.com/docs/ for authentication and options. This cURL request saves a WebP screenshot:
Best Value
curl -G "https://api.screenshotneo.com/v1/shot" -d access_key=YOUR_API_KEY --data-urlencode url=https://stripe.com -o shot.webp
The same request in Python is:
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)
And in Node.js:
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 data = Buffer.from(await res.arrayBuffer());
require('node:fs').writeFileSync('shot.webp', data);
ScreenshotNeo is not a substitute when you need the exact pixel buffer from a private, unsaved canvas; the page must be reachable at a URL. It is useful when you need a rendered-page capture without maintaining browser automation. Its MCP server exposes take_screenshot, get_page_info, and capture_pdf to Claude, Cursor, and other MCP clients. Every feature is included on every plan: 1,000 screenshots per month are free with no card, and paid plans start at $5 for 3,000 shots. Create a free ScreenshotNeo account to start.
Frequently Asked Questions
Can I export only part of a canvas?
Draw the desired region into a second canvas with drawImage(source, sx, sy, sw, sh, 0, 0, sw, sh), then export that second canvas with the same toBlob() method.
Does exporting include CSS filters applied to the canvas element?
No. Export serializes the canvas bitmap. CSS applied to the element changes its on-screen presentation, not the pixels stored in the bitmap; render the effect into the canvas if it must appear in the PNG.
Can a user choose the filename and location?
The download attribute suggests a filename, but the browser and operating system control the final save dialog, download folder, and any filename sanitization.
Quick Recap
Product prices and availability are accurate as of the date/time indicated and are subject to change. Any price and availability information displayed on Amazon at the time of purchase will apply.

