How to Download Images Generated with html-to-image.js
Save html-to-image.js output as PNG, JPEG, Blob, SVG, canvas, or pixels with runnable browser code, options, fixes, and a hosted API alternative.
Direct answer: html-to-image returns a promise. Call toPng, toJpeg, toBlob, or another output function for a DOM node, then save the resolved value. PNG and JPEG functions return data URLs; toBlob returns a PNG Blob.
1. Install html-to-image
Install the package in your frontend project:
npm install --save html-to-image
The package includes TypeScript declarations and is released under the MIT license. Import the function that matches the file or data type your application needs.
2. Download a DOM node as PNG
This complete browser example renders an element and downloads the returned data URL. The download must happen inside the promise continuation or after await.
import { toPng } from 'html-to-image';
const node = document.getElementById('my-node');
if (!node) {
throw new Error('Element #my-node was not found');
}
toPng(node)
.then((dataUrl) => {
const link = document.createElement('a');
link.download = 'my-node.png';
link.href = dataUrl;
link.click();
})
.catch((error) => {
console.error('Could not create PNG:', error);
});
The README also shows the same pattern with a download helper:
import { toPng } from 'html-to-image';
const node = document.getElementById('my-node');
toPng(node)
.then((dataUrl) => download(dataUrl, 'my-node.png'))
.catch((error) => console.error('oops, something went wrong!', error));
3. Download JPEG output
Use toJpeg when a compressed photograph-like image is more useful than a lossless PNG. Set quality deliberately; it accepts a value from 0 to 1, and the documented default is 1.
import { toJpeg } from 'html-to-image';
const node = document.getElementById('my-node');
if (!node) {
throw new Error('Element #my-node was not found');
}
toJpeg(node, { quality: 0.95 })
.then((dataUrl) => {
const link = document.createElement('a');
link.download = 'my-image-name.jpeg';
link.href = dataUrl;
link.click();
})
.catch((error) => console.error('Could not create JPEG:', error));
Use a .jpeg or .jpg filename for JPEG output. Lower quality usually produces a smaller file, while higher quality preserves more detail.
4. Download a Blob with FileSaver
Choose toBlob when your upload or save flow expects a Blob rather than a data URL. The documented result is a PNG Blob.
import { toBlob } from 'html-to-image';
const node = document.getElementById('my-node');
if (!node) {
throw new Error('Element #my-node was not found');
}
const blob = await toBlob(node);
if (!blob) {
throw new Error('html-to-image did not return a Blob');
}
if (window.saveAs) {
window.saveAs(blob, 'my-node.png');
} else {
FileSaver.saveAs(blob, 'my-node.png');
}
Use the FileSaver integration available in your application. A Blob is also convenient for fetch uploads, object URLs, and APIs that accept multipart form data.
5. Pick the right output function
| Function | Result | Use it when |
|---|---|---|
toPng |
PNG data URL | You need a lossless image download or an <img> source. |
toJpeg |
JPEG data URL | You want compression and can choose a quality value. |
toBlob |
PNG Blob | Your save or upload API expects a Blob. |
toSvg |
SVG data URL | You need the SVG data URL itself. |
toCanvas |
Canvas element | You need to draw, inspect, or further process a canvas. |
toPixelData |
Raw RGBA pixel bytes | You are doing pixel-level processing or analysis. |
Match the filename extension to the selected output format. A data URL is not a file until you assign it to a link, pass it to a helper, or otherwise persist it.
6. Control dimensions, background, and image loading
Rendering options can change the generated result:
import { toPng } from 'html-to-image';
const node = document.getElementById('my-node');
const dataUrl = await toPng(node, {
backgroundColor: '#ffffff',
width: 1200,
height: 630,
cacheBust: true,
imagePlaceholder: 'data:image/svg+xml,%3Csvg xmlns="http://www.w3.org/2000/svg" width="32" height="32"/%3E'
});
const link = document.createElement('a');
link.download = 'card.png';
link.href = dataUrl;
link.click();
backgroundColorsupplies a background color.widthandheightset the rendered dimensions.cacheBust: trueappends the current time as a query string to image requests.imagePlaceholdersupplies a data URL when an image cannot be fetched.
Make sure fonts, images, and dynamic content are ready before calling the conversion function. If the node changes after the call starts, those later changes will not be included.
7. Common errors and fixes
| Symptom | Likely cause | Fix |
|---|---|---|
| The result is blank or missing content | The node is empty, hidden, or captured before its content renders. | Check the selector, render the element visibly, and wait until data and fonts are loaded. |
| Images are absent | The browser cannot fetch an image for the canvas/SVG conversion, often because of cross-origin restrictions. | Serve images with suitable cross-origin headers, use same-origin assets, enable cacheBust, or provide imagePlaceholder. |
toBlob returns no value |
The conversion failed or the node cannot be rendered. | Check the rejected promise, verify the node exists, and reduce problematic external resources. |
| Download opens instead of saving | The browser blocks programmatic downloads outside a user gesture. | Start the conversion from a button click and trigger the anchor click after the promise resolves. |
| Text or fonts look different | The web font was not loaded when capture began. | Wait for document.fonts.ready before calling toPng or toJpeg. |
| JPEG has an unexpected file size | quality is too high or the source contains detailed imagery. |
Choose a deliberate quality value between 0 and 1 and measure the resulting file size. |
await document.fonts.ready;
const node = document.getElementById('my-node');
const dataUrl = await toPng(node);
8. Performance and reliability checklist
- Capture only the required node instead of a large application root.
- Set explicit dimensions when the output must have a predictable size.
- Wait for asynchronous data, images, and fonts before conversion.
- Use JPEG quality settings when bandwidth or storage matters.
- Use a Blob for uploads to avoid keeping a large base64 string in memory.
- Handle the rejected promise and show a retry path for users.
- Use
imagePlaceholderwhen a missing remote image should not fail the whole visual. - Use
cacheBustwhen stale image responses are producing outdated output.
Large nodes, high dimensions, many images, and data URLs all increase memory use. For repeated captures, release object URLs you create and avoid retaining old data URLs in application state.
9. Or skip the browser setup
If you need a screenshot of a public URL rather than a DOM node already inside your app, ScreenshotNeo provides a hosted screenshot API. It accepts one GET request and returns PNG, JPEG, WebP, or PDF output. Read the ScreenshotNeo API docs for the full option list.
ScreenshotNeo removes cookie and consent banners, newsletter popups, and chat widgets before capture. Bot checks, blank pages, failed loads, timeouts, and cache hits are not billed, and the response identifies the page verdict and billing status in headers. Its MCP server lets Claude, Cursor, and other MCP clients call screenshot tools directly.
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)
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 bytes = Buffer.from(await res.arrayBuffer());
require('node:fs').writeFileSync('shot.webp', bytes);
ScreenshotNeo includes full-page and element capture, custom CSS and JavaScript, device presets, waiting rules, request blocking, headers and cookies, PDF options, caching, async jobs, bulk capture, signed links, and usage reporting. The free plan includes 1,000 screenshots a month with no card; paid plans start at $5 for 3,000 shots. Sign up free for ScreenshotNeo.
10. FAQ
Can I download without FileSaver?
Yes. For PNG or JPEG, assign the returned data URL to an anchor’s href, set download, and call click(). FileSaver is useful when you already have a Blob.
Does toBlob create JPEG?
The documented toBlob output is a PNG Blob. Use toJpeg for JPEG data URLs.
Why does my filename not match the image format?
The filename is controlled by your anchor or save helper. Set a matching extension yourself, such as .png for toPng and .jpeg for toJpeg.
Can I get raw pixels instead of a downloadable file?
Yes. Use toPixelData when your code needs RGBA bytes rather than an image file.


