If html2canvas omits S3 images or throws Failed to execute 'toDataURL', the usual cause is a canvas tainted by an image response that did not authorize your page’s origin. Set useCORS: true, then configure CORS on the endpoint the browser actually calls (S3 or CloudFront). Keep allowTaint disabled when you need to export pixels. If the endpoint cannot be changed, route images through a same-origin proxy.
What the error means
html2canvas rebuilds a visual representation from the DOM; it does not capture the browser’s composited screen and cannot bypass browser security rules. When an image from another origin is drawn without a successful CORS response, the browser marks the canvas as tainted. Pixel reads and export methods such as toDataURL(), toBlob(), and getImageData() are then blocked.
With its documented defaults, useCORS is false and allowTaint is false. The latter causes html2canvas to skip an image that would taint the canvas. Setting allowTaint: true does not make the pixels exportable; it allows a tainted canvas, which is the opposite of the requirement when you need a PNG, JPEG, or pixel data.
A public S3 object can therefore display in an <img> while remaining unusable in a canvas. Object authorization and CORS authorization are separate controls.
#1 Best Overall
1. Find the real image endpoint first
- Open the page containing the capture target.
- In browser developer tools, select Network, filter by Img, and reload.
- Open the image request that fails or is skipped. Record the final URL after redirects, status code, and response headers.
Determine whether the browser receives the file from an S3 REST endpoint, an S3 website endpoint, or a CloudFront distribution. Test the exact browser-facing URL, not merely the bucket URL you expect to be used. Check the response’s Access-Control-Allow-Origin. For a credential-free image request, it must authorize the page origin (for example, https://app.example.com), or use an appropriate wildcard for genuinely public, non-credentialed use.
Also inspect the request’s Origin, method, and any requested headers. A redirect can move the request to a different host where CORS headers are absent.
2. Tell html2canvas to request the image with CORS
Enable the library option on the capture call:
import html2canvas from 'html2canvas';
const element = document.querySelector('#report');
const canvas = await html2canvas(element, {
useCORS: true,
});
const png = canvas.toDataURL('image/png');
useCORS only tells html2canvas to attempt a CORS image load. It cannot add Access-Control-Allow-Origin to an S3 response. Every image included in the DOM, including images inside nested elements, must be served with a response that the browser accepts.
Use the project’s documented configuration and limitations pages for the complete option list: configuration, getting started, and limitations.
Quick wins for a faster PC:
Repair Windows errors before they cause bigger problemsFix Now →Fix the driver behind crashes, sound loss and screen glitchesFind Drivers →3. Configure the S3 bucket CORS rule
In the S3 console, open the bucket, choose Permissions, then Cross-origin resource sharing (CORS), and save a JSON rule that matches the page origin and image method. A least-privilege example is:
Rank #2
[{
"AllowedOrigins": ["https://app.example.com"],
"AllowedMethods": ["GET"],
"AllowedHeaders": []
}]
Replace the origin with the exact scheme, host, and port shown in the page’s Origin request header. http://localhost:3000 and https://app.example.com are different origins. Add only request headers your application actually sends. If the browser performs a preflight, those headers must be allowed; do not add a broad wildcard simply because an example used one.
For a public, credential-free image service, "AllowedOrigins": ["*"] can be valid. A specific origin is easier to audit and prevents a broad policy from being mistaken for a requirement. CORS does not grant read permission: the object still needs an applicable bucket policy, ACL, or other S3 authorization.
S3 evaluates the first CORS rule matching the request’s origin, method, and requested headers. Rule order therefore matters. AWS documents the matching behavior and the testing procedure in its CORS overview and CORS testing guide.
Verify a preflight when one occurs
Most simple image GETs do not require preflight, but custom headers, credentials, or non-simple methods can trigger an OPTIONS request. Reproduce it from a shell:
curl -i -X OPTIONS 'https://bucket.s3.amazonaws.com/images/logo.png'
-H 'Origin: https://app.example.com'
-H 'Access-Control-Request-Method: GET'
-H 'Access-Control-Request-Headers: authorization'
The response must match the requested origin, method, and headers. If the rule does not match, S3 returns no usable CORS headers; editing JavaScript cannot fix that response.
4. Handle CloudFront correctly
If the page uses a CloudFront URL, configure and test CloudFront, even when direct S3 access works. The distribution must forward Origin to the S3 origin so S3 can generate the correct response. For cached preflight responses, forward Origin, Access-Control-Request-Method, and Access-Control-Request-Headers, and make the cache behavior vary on those inputs. Otherwise CloudFront can cache a response without the needed CORS header and reuse it for another origin.
Compare headers at both endpoints:
curl -I 'https://bucket.s3.amazonaws.com/images/logo.png'
-H 'Origin: https://app.example.com'
curl -I 'https://cdn.example.com/images/logo.png'
-H 'Origin: https://app.example.com'
If S3 includes Access-Control-Allow-Origin but CloudFront does not, investigate origin forwarding, the distribution’s cache policy, response-header policy, and stale cached objects. AWS’s CloudFront origin and CORS guidance and S3 CORS troubleshooting cover these interactions.
Windows Errors? Fix Them Before They Spread
Repair common Windows errors and clear accumulated junk for a smoother, more stable PC - no reinstall needed.Free scan · no reinstallOutdated Drivers Are Slowing You Down
One free scan finds every outdated or missing driver and matches the right update for your exact hardware.Free scan · exact hardware match5. Retest and isolate the remaining asset
- Hard-refresh the page or clear the relevant CDN cache after changing headers.
- Capture a minimal element containing one known-good S3 image.
- Confirm the image response contains the expected CORS header and that html2canvas is called with
useCORS: true. - Add other images, CSS backgrounds, SVGs, and canvases one at a time.
- Only after the capture succeeds, call
toDataURL,toBlob, or pixel-reading APIs.
A single unapproved image or a pre-existing tainted canvas anywhere in the captured subtree can taint the final canvas. Browser behavior is enforced by the security model, not by html2canvas. The project lists current Chrome/Chromium, Firefox, and Safari as supported evergreen browsers; still compare the actual network response in every browser you target.
Common symptoms and precise fixes
S3 image is missing from the capture
- Confirm the URL is reachable and returns an image, not an access-denied page or redirect.
- Check that the final response has an origin-authorizing header.
- Enable
useCORS: true. With the defaultallowTaint: false, html2canvas may skip an image that would taint the canvas. - If you cannot configure the image host, use a same-origin proxy that fetches the asset server-side and serves it from your application origin.
toDataURL or toBlob throws a security error
Find every cross-origin image and canvas in the captured subtree. One asset loaded without CORS approval is enough to taint the result. Fix that response or remove the asset before capture; changing allowTaint will not restore export access.
The object returns 403 or 404
Resolve S3 authorization, object key, region, and redirect issues separately from CORS. A CORS rule does not make a private object public.
Rank #4
Direct S3 works but CloudFront fails
Inspect the distribution response, not just the origin. Forward the CORS request headers, vary cached responses appropriately, and invalidate or wait out an object whose response was cached without CORS headers.
Free tools Windows power users keep installed
One-click scans. No signup required.
The rule looks correct but no CORS header appears
Compare the exact Origin, method, and requested headers with the rule; check that an earlier rule is not matching first; and test the endpoint with an OPTIONS request when preflight is present.
It works in one browser but not another
Capture the network trace in each browser and compare redirects, final URLs, request mode, and response headers. Do not assume a library flag can bypass browser enforcement. Also check for browser extensions, service workers, or cached CDN responses that differ between profiles.
S3 CORS versus a same-origin proxy
| Approach | Best when | What you control | Costs and risks |
|---|---|---|---|
| S3/CloudFront CORS | You own the asset delivery configuration | Origin, methods, headers, and CDN cache variation | Requires correct deployment and cache configuration; object authorization remains separate |
| Same-origin proxy | The asset host cannot return the required headers | Your server’s response headers and fetch policy | Adds server bandwidth, latency, validation, and SSRF/access-control responsibilities |
Neither method is universally faster or safer. Choose the control point you can operate reliably, restrict proxy destinations if you use one, and avoid turning a private-image proxy into an unrestricted URL fetcher.
Performance and reliability notes
- Start with a small element and one image to separate CORS from layout, font, and memory problems.
- Large full-page DOMs and high-resolution images increase canvas memory use; capture only the required region when possible.
- CloudFront caching can improve repeat loads, but cached CORS responses must vary on the relevant request headers.
- After changing S3 or CloudFront configuration, verify the response observed by the browser rather than relying on console settings alone.
- Record the final URL, status, and CORS headers in bug reports; these facts usually identify the failing layer quickly.
Or skip the browser setup
If your goal is a dependable website image rather than a DOM reconstruction, ScreenshotNeo provides a single HTTP request. It accepts cookie and consent banners before capture 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 identifies the result with X-Page-Verdict and X-Billed headers. Its MCP server exposes take_screenshot, get_page_info, and capture_pdf to Claude, Cursor, and other MCP clients.
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 →Use the API documentation at screenshotneo.com/docs/. The following call captures a clean WebP image:
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
Equivalent Python:
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)
Equivalent 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}`);
const file = Buffer.from(await res.arrayBuffer());
Every plan includes the available features, including full-page and element capture, device presets, retina scale, PDF controls, custom CSS and JavaScript, selector waits, request blocking, headers and cookies, timezone and geolocation, resizing, TTL caching, signed links, asynchronous webhooks, bulk capture, usage information, and an OpenAPI specification. Pricing is Free for 1,000 shots per month with no card, then Starter $5 for 3,000, Growth $15 for 15,000, Pro $39 for 60,000, Scale $99 for 250,000, and Business $249 for 1,000,000; annual billing provides two months free. Create a free ScreenshotNeo account to start with 1,000 screenshots a month and no card.
Reference documentation
- html2canvas FAQ
- html2canvas configuration
- Amazon S3 CORS overview
- Amazon S3 CORS testing
- MDN: Use cross-origin images in a canvas
Frequently Asked Questions
Does adding crossorigin="anonymous" alone fix an S3 image?
No. It requests a CORS-enabled load, but S3 or CloudFront must still return an Access-Control-Allow-Origin value that matches the page origin.
Can I use credentials with a wildcard origin?
Credentialed cross-origin requests require an explicit authorized origin; a wildcard is not a substitute. Configure credentials and allowed headers only when your application actually needs them.
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 problemsWhy does a normal browser tab display the image when html2canvas cannot export it?
Displaying an image does not grant script access to its pixels. Canvas export is allowed only after the image response passes the browser’s CORS checks.
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.




