ScreenshotNeo

BlogHow-to

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.

By the ScreenshotNeo team1 October 20266 min read

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();
  • backgroundColor supplies a background color.
  • width and height set the rendered dimensions.
  • cacheBust: true appends the current time as a query string to image requests.
  • imagePlaceholder supplies 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 imagePlaceholder when a missing remote image should not fail the whole visual.
  • Use cacheBust when 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.