Short answer: a remote image must grant your React page permission through CORS, and html2canvas must request it with useCORS: true. If the image server cannot be changed, retrieve an authorized copy through a restricted same-origin proxy. Setting allowTaint: true does not make a tainted canvas downloadable.
Why images disappear or downloads fail
html2canvas rebuilds a picture from the captured DOM; it does not take a privileged, pixel-perfect browser screenshot. When an <img> comes from a different origin (scheme, hostname, or port), the browser applies its canvas security rules. The page may display that image normally, yet drawing it into a canvas without CORS approval makes the canvas tainted. Reading pixels or exporting with toDataURL() or toBlob() then raises a SecurityError. MDN describes this rule in Use cross-origin images in a canvas.
React does not create an exception to browser CORS. It only supplies the mounted DOM node that html2canvas captures. The html2canvas project states that it cannot circumvent browser content-policy restrictions in its FAQ.
The normal fix: CORS on the image host plus useCORS
1. Identify the failing origin
Open Developer Tools, inspect every image inside the element, and compare each URL with the page URL. A same-origin URL, a data: URL, and a remote URL follow different paths. In the Network panel, reload the page and inspect the image response for CORS errors or a missing Access-Control-Allow-Origin header. Do not assume a missing image is a CORS problem until you identify the specific request.
#1 Best Overall
2. Permit your application origin on the server
Configure the image origin or CDN to return Access-Control-Allow-Origin for the exact origin of your React app, such as https://app.example.com. A wildcard (*) is appropriate only when the asset and credential model allow it. If cookies or other credentials are required, use an explicit origin and the server’s credential settings; do not combine a wildcard with credentialed requests. The response header is the authority—client JavaScript cannot manufacture it.
3. Ask html2canvas to load images in CORS mode
Pass useCORS: true in the options object. Make sure the reference is the intended, mounted element and wait until its images have loaded.
import html2canvas from 'html2canvas';
async function downloadCard() {
const element = document.querySelector('#card');
if (!element) throw new Error('Capture element was not found');
const canvas = await html2canvas(element, {
useCORS: true
});
const blob = await new Promise((resolve, reject) => {
canvas.toBlob(result => result ? resolve(result) : reject(new Error('Canvas export failed')), 'image/png');
});
const link = document.createElement('a');
link.href = URL.createObjectURL(blob);
link.download = 'card.png';
link.click();
URL.revokeObjectURL(link.href);
}
The option tells html2canvas to attempt a CORS request; it does not authorize a server that sends no permissive header. For an image rendered directly by React, crossOrigin="anonymous" can cause the browser to request it in CORS mode, but it must be paired with matching server permission:
<img src={imageUrl} crossOrigin="anonymous" alt="Product" />
Adding this attribute alone cannot fix a server that omits CORS headers. In most html2canvas captures, useCORS: true is the key configuration.
Rank #2
When you cannot change the image server: use a controlled proxy
html2canvas documents a proxy option in its Getting Started guide and FAQ. Your server fetches permitted images and serves them from your own origin, so the browser sees a same-origin resource.
const canvas = await html2canvas(element, {
useCORS: true,
proxy: 'https://app.example.com/api/image-proxy'
});
A production proxy must not become an unrestricted URL-fetching endpoint. Accept only destinations your application is authorized to retrieve, validate and restrict hostnames, follow redirects safely, enforce response-size and time limits, preserve the correct image content type, and return explicit errors. Authentication requirements, hotlink protection, redirects, and the remote host’s policy can still prevent an image from being usable. A proxy is a server-side responsibility, not a way to bypass another site’s access controls.
Illustrative proxy flow
- The browser sends an approved image identifier to your backend, not an arbitrary URL.
- The backend maps that identifier to an allowlisted host and downloads the resource with timeout and size limits.
- The backend verifies that the response is an allowed image type, then returns the bytes with that content type from your own origin.
- The React page points the captured image at this endpoint and captures with the proxy option.
If neither CORS configuration nor a permitted proxy is possible, use a same-origin copy you are licensed to store, or exclude that image from the capture. A tainted canvas is incompatible with reliable downloads.
Why allowTaint: true is not an export fix
The option name is easy to misread. html2canvas’s default allowTaint value is false; in that mode it skips images it determines would taint the canvas. Turning it on allows drawing the image, but it does not grant permission to read the resulting pixels. MDN notes that once foreign data is drawn without CORS approval, export and read methods are blocked by a SecurityError. For a downloadable file, keep the canvas origin-clean instead.
The Tool Desk
Outbyte PC Repair FREEClear out junk files and repair common Windows errorsFree Scan →Outbyte Driver Updater FREEScan for outdated or missing drivers - takes under a minuteDriver Scan →Capture settings that solve different problems
After CORS is correct, use the configuration options documented at html2canvas Options for ordinary loading and layout issues:
- Wait for content: capture after React state updates and image load events. A delay or a selector wait can help with late-rendered content, but neither bypasses CORS.
imageTimeout: adjust the image-load timeout when a legitimate image is slow. A timeout is not evidence of a CORS grant.onError: log resource failures so you can distinguish a failed URL from a policy rejection.- Viewport dimensions: for clipping, match
windowWidthandwindowHeightto the element or document’s scroll dimensions as recommended in the FAQ. Very large canvases can also hit browser size limits. - Full-page and layout options: use them only after image requests are working; changing scale, background, or width cannot repair a missing response header.
The project’s examples show the capture interface and option placement.
A repeatable diagnosis checklist
- Confirm the node: verify that the selector resolves to the mounted element you intend to download.
- List its images: inspect
img.currentSrcvalues and note each scheme, host, and port. - Check the response: confirm the remote response includes an
Access-Control-Allow-Originvalue that matches your app’s origin. - Enable CORS loading: call html2canvas with
useCORS: true; addcrossOrigin="anonymous"to directly rendered images when appropriate. - Capture after loading: wait for the relevant image promises and React render to complete.
- Export and inspect: call
toBlob; if it throwsSecurityError, find the remaining foreign image rather than enablingallowTaint. - Choose the fallback: configure the host, use a restricted proxy, use an authorized same-origin copy, or omit the image.
Common errors and precise fixes
| Symptom | Likely cause | Fix |
|---|---|---|
| Image is blank in the canvas | Remote request lacks CORS permission, or the URL failed | Inspect the Network response; configure the image host and use useCORS: true, or use an authorized proxy. |
SecurityError from toBlob() or toDataURL() |
The canvas is tainted by at least one foreign resource | Remove or replace the offending image, obtain CORS approval, or proxy it. Do not rely on allowTaint. |
crossOrigin appears correct but nothing changes |
The server still sends no matching Access-Control-Allow-Origin |
Fix the response policy on the origin/CDN; the attribute cannot add a header. |
| Capture completes before an image appears | React or the image loaded after capture began | Wait for state/render completion and image load; then capture. Consider a delay or selector wait for late content. |
| Only a very large page is clipped or fails | Browser canvas dimension limits or viewport mismatch | Match windowWidth/windowHeight to scroll dimensions and capture smaller regions when necessary. |
| Proxy returns an unusable result | Open proxy, redirect, authentication, content-type, or size problem | Allowlist destinations, validate redirects and content types, enforce limits, and return explicit errors. |
Reliability, performance, and security considerations
- Keep the browser path direct when possible: host-level CORS avoids an extra server hop and proxy maintenance.
- Expect network variability: remote images may be slow, rate-limited, protected against hotlinking, or unavailable. Set sensible timeouts and surface failures to users.
- Reduce capture cost: capture the smallest required element, avoid unnecessarily huge dimensions, and ensure images are appropriately sized before rendering.
- Protect proxy credentials and data: do not forward arbitrary authorization headers from the browser, and apply authentication and rate limits to your endpoint.
- Treat third-party content as untrusted: a successful HTTP response does not prove it is an allowed image or safe destination.
Or skip the browser setup
For server-side captures, ScreenshotNeo provides a website screenshot API and MCP server. One request returns a PNG, JPEG, WebP, or PDF, without relying on your React page’s canvas permissions. Before capture it accepts cookie or consent banners and removes more than 60 known consent platforms, newsletter popups, and chat widgets; each step can be disabled. Bot checks, CAPTCHAs, blank pages, timeouts, failed loads, and cache hits are not billed, and the response identifies the page and billing result with X-Page-Verdict and X-Billed headers. Its MCP server includes take_screenshot, get_page_info, and capture_pdf tools for Claude, Cursor, and other MCP clients.
Example using the documented API at ScreenshotNeo docs:
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)
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}`);
ScreenshotNeo supports full-page captures with lazy images loaded, CSS-selector element capture, dark mode, 12 device presets plus custom viewports, retina scale, PDF controls, custom CSS and JavaScript, clicks, waits, hidden selectors, request and resource blocking, headers, cookies, user agents, authorization, timezone and geolocation, transparent backgrounds, resizing, configurable-TTL caching, signed links, asynchronous jobs with signed webhooks, bulk capture of up to 100 URLs per call, a usage API, and an OpenAPI specification. Parameter names used by other screenshot APIs also work, which can simplify migration.
Rank #4
The Free plan includes 1,000 screenshots per month with no card. Paid plans start at $5 for 3,000 shots; all features are available on every plan, and yearly billing gives two months free. Create a free ScreenshotNeo account to try it.
Independent reader supportYour contribution helps us test, update, and keep practical guides available for everyone.FAQ
Does a successful image request prove CORS is configured?
No. A browser can display a cross-origin image while still refusing to expose its pixels to a canvas. The response must include a suitable CORS header, and the image must be requested in CORS mode.
Can I solve this entirely in React?
No. React can set html2canvas options and image attributes, but only the image server or an authorized server-side proxy can provide the required permission.
PC Slower Than It Used to Be?
A free scan shows the junk files, broken settings and background clutter dragging Windows down - then fixes them in one click.Free scan · Windows 10 & 11Crashes, No Sound, or Screen Glitches?
Random freezes, missing sound and display glitches usually trace back to one bad driver. Find and replace yours safely.Free scan · under a minuteWhy does the downloaded file work for some cards but not others?
Each captured image is evaluated independently. One card may use same-origin or approved assets while another includes a foreign image that taints the shared canvas.
Best Value
Is html2canvas a native screenshot tool?
No. It reconstructs the scene from DOM information, so browser policies, unsupported content, and canvas limits still apply. For a remotely hosted page, a server-side screenshot service can avoid this client-canvas path.
Frequently Asked Questions
Does a successful image request prove CORS is configured?
No. Display permission and canvas pixel permission are different; the response needs a suitable CORS header and a CORS-mode request.
Can I solve this entirely in React?
No. React can configure html2canvas, but the image host or an authorized proxy must provide permission.
Quick wins for a faster PC:
Repair Windows errors before they cause bigger problemsFix Now →Scan for outdated or missing drivers - takes under a minuteDriver Scan →Why does the downloaded file work for some cards but not others?
A single foreign image can taint the canvas, while cards containing same-origin or approved assets remain exportable.
Is html2canvas a native screenshot tool?
No. It reconstructs content from the DOM and remains subject to browser policies and canvas limits.
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.

