What’s actually slowing this PC down?

Pick the symptom - the matching free tool is one click away.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Use useCORS: true only when the image server sends a suitable Access-Control-Allow-Origin header. If you cannot change that server, load the image through a same-origin proxy. Keep allowTaint disabled when you need to export the canvas: allowing a tainted canvas does not make it readable.

html2canvas does not take a photograph of the browser window. It walks the DOM and paints a canvas from the elements and CSS properties it understands. That distinction matters for remote images: an image can be visible in the page while still being forbidden from canvas readback by the browser’s same-origin policy.

The reliable choices are therefore limited:

  • Configure the image host to allow your page origin, then use useCORS: true.
  • Fetch the image through infrastructure you control and serve it from your own origin, then configure html2canvas with proxy.

There is no html2canvas flag that overrides browser content policy. The project’s documentation states: “html2canvas cannot circumvent browser content policy restrictions.”

Why an external image can be visible but unavailable to canvas

Browsers define an origin by scheme, host and port. An image at another origin may render normally in an <img> element, yet drawing it into a canvas can make that canvas tainted. A tainted canvas cannot be inspected or exported with APIs such as toDataURL().

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

html2canvas normally avoids images it expects would taint the result. Its documented defaults are:

Option Default Meaning
useCORS false Do not attempt a CORS image request unless enabled.
allowTaint false Do not draw images that would taint the canvas.
proxy null No proxy URL is used.

These defaults are version-sensitive, so check the configuration reference for the html2canvas release installed in your project.

Route 1: enable CORS on the image host

Choose this route when you control the image server, or when its operator already permits requests from your application. The server must return an appropriate Access-Control-Allow-Origin response header; setting a client option cannot create that permission.

Minimal html2canvas call

const element = document.querySelector('#invoice');

const canvas = await html2canvas(element, {
  useCORS: true,
  allowTaint: false
});

const png = canvas.toDataURL('image/png');
const link = document.createElement('a');
link.download = 'invoice.png';
link.href = png;
link.click();

Use the same option when you need a JPEG or another canvas export method. The important requirement is that every remote image included in the rendered subtree is loaded with a response the browser accepts for CORS.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Make the image request CORS-aware

If you create or replace an image element yourself, set its crossOrigin property before assigning src:

const image = new Image();
image.crossOrigin = 'anonymous';
image.src = 'https://images.example.test/photo.jpg';
await image.decode();
document.querySelector('#preview').append(image);

The property does not grant access by itself. The response still needs a matching Access-Control-Allow-Origin header, usually naming your page’s origin or using a policy appropriate for the asset.

Verify the actual response

  1. Open browser developer tools and inspect the image request in the Network panel.
  2. Confirm that the request URL is the one you expect, including redirects.
  3. Inspect the response headers for Access-Control-Allow-Origin.
  4. Render the element again and test the export operation.

A header on your HTML page or on a different CDN path does not fix the image response. Redirects, alternate hosts and signed URLs can cause the final response to have different headers.

Route 2: use a same-origin image proxy

Use a proxy when the external image host cannot be configured for your required CORS policy. Your application retrieves the image, then exposes it to the browser through the same origin as the page. html2canvas’s getting-started documentation demonstrates configuring a proxy URL and an endpoint that accepts a ?url= query and returns the resource as a base64 data URI.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Client configuration

const canvas = await html2canvas(document.querySelector('#invoice'), {
  proxy: '/image-proxy',
  allowTaint: false
});

const png = canvas.toDataURL('image/png');

The endpoint above is illustrative. Implement and host it as part of your application; do not assume that an arbitrary public proxy is trustworthy or officially supported.

What the proxy must do

  1. Accept an image URL from your application.
  2. Retrieve the resource server-side.
  3. Validate the response and content type.
  4. Return the bytes (or the data-URI form expected by your implementation) from your own origin.
  5. Preserve a correct image media type so the browser can decode it.

Because the proxy becomes part of your production infrastructure, define an allowlist of hosts or URL patterns, enforce request limits, handle timeouts, and decide how to cache responses. Without those controls, a URL-fetching endpoint can be abused to request internal services or consume excessive bandwidth. The html2canvas documentation describes the architectural route but does not prescribe your access-control, rate-limit or caching policy.

Illustrative Express endpoint

The following is a starting point, not a drop-in security policy. It deliberately restricts hosts and limits the response size; adapt it to your deployment and authentication model.

import express from 'express';

const app = express();
const allowedHosts = new Set(['images.example.test']);

app.get('/image-proxy', async (req, res) => {
  try {
    const target = new URL(String(req.query.url || ''));
    if (target.protocol !== 'https:' || !allowedHosts.has(target.hostname)) {
      return res.status(400).send('Image host is not allowed');
    }

    const upstream = await fetch(target, { redirect: 'follow' });
    if (!upstream.ok) return res.status(502).send('Image request failed');

    const type = upstream.headers.get('content-type') || '';
    if (!type.startsWith('image/')) return res.status(415).send('Not an image');

    const data = Buffer.from(await upstream.arrayBuffer());
    if (data.byteLength > 10 * 1024 * 1024) {
      return res.status(413).send('Image is too large');
    }

    res.set('Content-Type', type);
    res.set('Cache-Control', 'private, max-age=300');
    res.send(data);
  } catch {
    res.status(400).send('Invalid image URL');
  }
});

app.listen(3000);

Point proxy at this route only after adding the controls your application needs. If your proxy returns a data URI rather than raw bytes, follow the format used by the html2canvas getting-started example.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Why allowTaint: true does not solve exports

allowTaint controls whether html2canvas may draw an image that would taint the canvas. It does not make the canvas readable. If any image taints the result, calls such as canvas.toDataURL('image/png') can fail with a security exception. Leave it false whenever the result must be downloaded, uploaded or inspected.

Also check for canvases already present inside the element. A pre-existing tainted canvas can contaminate the new rendering even when the external image you are investigating has correct headers.

Complete browser example with a fallback decision

This example tries the direct CORS route and reports a useful error instead of silently producing an incomplete image:

async function capture(selector) {
  const element = document.querySelector(selector);
  if (!element) throw new Error(`No element matches ${selector}`);

  const canvas = await html2canvas(element, {
    useCORS: true,
    allowTaint: false,
    imageTimeout: 15000
  });

  try {
    return canvas.toDataURL('image/png');
  } catch (error) {
    throw new Error(
      'The canvas is not readable. Check every remote image response for CORS headers, or use a same-origin proxy.'
    );
  }
}

capture('#invoice')
  .then(dataUrl => {
    const link = document.createElement('a');
    link.download = 'invoice.png';
    link.href = dataUrl;
    link.click();
  })
  .catch(console.error);

imageTimeout is an application-level example; tune it for your page and installed html2canvas version. A timeout does not repair a CORS violation.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Troubleshooting: symptom, cause and fix

Symptom Likely cause Fix
Remote image is missing from the capture The image would taint the canvas and useCORS is off, or the CORS request was rejected. Inspect the final image response. Enable useCORS: true only after the host sends the required header, or route the image through your proxy.
useCORS is enabled but nothing changes The external server did not grant permission, or a redirect leads to a response without the header. Check the Network panel, redirects and the exact requested URL. Client JavaScript cannot alter that response policy.
Capture appears complete but toDataURL fails Some image or nested canvas tainted the result. Keep allowTaint: false, remove or proxy the offending resource, and test nested canvases separately.
Proxy requests return errors Invalid URL, disallowed host, upstream timeout, non-image content or an oversized response. Log the upstream status and content type, validate URLs, allow only required hosts and return clear 4xx/5xx responses.
Layout differs from the browser html2canvas reconstructs the DOM and supports only the CSS properties it understands. Reduce unsupported styling, wait until fonts and images are ready, and treat the output as a rendered representation rather than a pixel-identical screenshot.

Reliability and performance considerations

Prefer direct CORS when you control the host

A direct request avoids a proxy hop and keeps the image on its normal delivery path. It is the simplest operational model, but every image origin in the captured subtree must cooperate.

Use a proxy for consistency

A proxy gives your application one policy boundary when images come from vendors you cannot configure. It also introduces server bandwidth, timeout and caching decisions. Cache only content your application is allowed to store, and avoid forwarding user-controlled credentials to arbitrary hosts.

Capture after resources are ready

Run html2canvas after the target element is present and after required images have loaded. For dynamically inserted images, wait for their load or decode() completion before capturing. Lazy-loaded images may otherwise be absent simply because they were never requested.

Remember html2canvas’s rendering scope

The library does not capture browser chrome, cross-origin content that policy blocks, or every CSS feature exactly as a browser compositor does. If you need a server-rendered page image or PDF without wiring a browser and proxy, a hosted capture service may be a better fit.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
Independent reader supportYour contribution helps us test, update, and keep practical guides available for everyone.Support on Ko-Fi

Or skip the browser setup

ScreenshotNeo provides a website screenshot API and MCP server. One request can return a PNG, JPEG, WebP or PDF, so your application does not need to configure html2canvas, CORS headers or an image proxy for a remote page.

curl -G "https://api.screenshotneo.com/v1/shot" -d access_key=YOUR_API_KEY --data-urlencode url=https://stripe.com -o shot.webp

See the ScreenshotNeo documentation for request options and response details. The equivalent Python and Node.js calls are:

import requests
r = requests.get("https://api.screenshotneo.com/v1/shot", params={"access_key": "YOUR_API_KEY", "url": "https://stripe.com"}, timeout=90)
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}`);
  • Cookie and consent banners, newsletter popups and chat widgets are removed before the shot; each cleanup step can be turned off.
  • Bot checks, blank pages, timeouts, failed loads and cache hits are not billed. Response headers identify the page verdict and billing result with X-Page-Verdict and X-Billed.
  • An MCP server provides take_screenshot, get_page_info and capture_pdf tools for Claude, Cursor and other MCP clients.
  • The Free plan includes 1,000 screenshots per month with no card. Paid plans start at $5 for 3,000 screenshots; yearly billing provides two months free, and every feature is included on every plan.

When your goal is a clean page capture rather than a DOM-rendered canvas, sign up for the free ScreenshotNeo plan and start without a card.

FAQ

Can I capture an image from another subdomain without a proxy?

Only if the image response is CORS-permitted for the page origin. A different subdomain is still a different origin when the host, scheme or port differs.

Free tools Windows power users keep installed

One-click scans. No signup required.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Does converting the image URL to a data URI in the browser bypass CORS?

No. The browser must first obtain the bytes. Fetching them from an origin that has not granted access is subject to the same policy.

What should I test when only one image fails?

Inspect that image’s final request and response independently. Mixed image hosts often have different headers, redirects or authentication requirements.

Is html2canvas suitable for a pixel-perfect archival screenshot?

Not always. It reconstructs a representation from DOM information and supported CSS, so browser rendering differences and unsupported properties can remain even after CORS is configured.

Frequently Asked Questions

Can I capture an image from another subdomain without a proxy?

Only when the image response grants CORS access to the page origin; a different subdomain is still a different origin.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Does converting the image URL to a data URI in the browser bypass CORS?

No. Obtaining the bytes is still subject to the browser’s cross-origin policy.

What should I test when only one image fails?

Inspect that image’s final request and response separately, including redirects and response headers.

Is html2canvas suitable for a pixel-perfect archival screenshot?

Not always; it rebuilds the DOM using supported CSS rather than capturing the compositor output exactly.

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.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.