To include an image that blocks cross-origin access, first try html2canvas with useCORS: true if the image server permits your page’s origin. If it does not, route the image request through a same-origin PHP endpoint and configure html2canvas’s proxy option. The endpoint must accept the image URL and return the fetched image as a base64 data URI. A proxy is a server-side fetcher, so it also needs strict URL and resource controls to avoid becoming an SSRF vulnerability.
Why html2canvas skips some external images
html2canvas reconstructs an element from the page’s DOM and styles, then draws the result in a browser canvas. It is not a browser-policy bypass. When an image comes from another origin, the browser’s same-origin and CORS rules determine whether it can be drawn in a way that still allows the canvas to be read or exported. The html2canvas FAQ states that the library cannot circumvent content-policy restrictions set by the browser.
In practice, the image may be omitted from the rendered result, or the canvas may become tainted. A tainted canvas cannot be read back through APIs such as toDataURL(). Setting allowTaint: true does not make a tainted canvas safely exportable. Use CORS permission from the image server or a proxy that supplies the image in the format html2canvas expects.
There are two separate origins to keep in mind: the origin of the page being captured and the origin serving the image. Images on the page’s own origin normally avoid this particular cross-origin problem. Images hosted elsewhere may work directly only when their responses authorize the page through CORS.
The Tool Desk
Outbyte PC Repair FREERepair Windows errors before they cause bigger problemsFix Now →Outbyte Driver Updater FREEScan for outdated or missing drivers - takes under a minuteDriver Scan →#1 Best Overall
Choose direct CORS or a PHP proxy
| Approach | When it fits | Trade-off |
|---|---|---|
useCORS: true |
The remote image server sends a suitable Access-Control-Allow-Origin response header for your page. |
Fewer moving parts and no proxy bandwidth, but you do not control whether the third-party server grants access. |
| PHP proxy | The image server does not provide the needed CORS permission, and your application can safely fetch the resource server-side. | Adds a server request, bandwidth and a security-sensitive URL-fetching endpoint that you must maintain. |
The html2canvas configuration reference lists useCORS as false by default and proxy as null by default. Unless you enable CORS or provide a proxy, html2canvas does not load cross-origin images through either of those mechanisms. The reference also lists imageTimeout as 15,000 milliseconds by default; that setting controls how long image loading is allowed to take, not whether the image is authorized.
Try the remote server’s CORS support first
If you control the remote image server, configure it to return an appropriate CORS header for the page’s origin. If you do not control it, ask its operator whether cross-origin use is supported. Then try the smallest configuration:
html2canvas(document.querySelector('#capture'), {
useCORS: true
}).then(canvas => {
document.body.appendChild(canvas);
});
This option is not a request to ignore CORS. It asks html2canvas to load images using CORS; the remote server still has to authorize the request. A wildcard or origin-specific response policy is a decision for the image server and its security requirements. If the response lacks suitable permission, use the proxy route instead.
Rank #2
Build a PHP endpoint for the proxy fallback
The documented html2canvas proxy model accepts a ?url= query parameter and returns the fetched resource as a base64 data URI. The browser-side proxy option points html2canvas at that endpoint. The PHP example below illustrates the contract while limiting requests to a fixed host allowlist. Change the allowlist to domains your application genuinely needs; do not accept arbitrary public URLs.
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 & 11Outdated 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 matchThis example requires PHP’s cURL extension and the Fileinfo extension. It rejects non-HTTPS URLs, credentials in URLs, unapproved hosts, redirects, non-image content types, and responses over 5 MB. The hostname allowlist is important, but it is not a complete substitute for network-level SSRF controls: production deployments should also prevent outbound connections to private, loopback, link-local, and metadata-service addresses, including after DNS resolution.
<?php
// proxy.php — allow only image hosts needed by your application.
$allowedHosts = ['images.example.com', 'cdn.example.net'];
$maxBytes = 5 * 1024 * 1024;
$url = $_GET['url'] ?? '';
function fail(int $status, string $message): never {
http_response_code($status);
header('Content-Type: text/plain; charset=utf-8');
exit($message);
}
if (!is_string($url) || $url === '' || !filter_var($url, FILTER_VALIDATE_URL)) {
fail(400, 'Invalid URL');
}
$parts = parse_url($url);
if ($parts === false || strtolower($parts['scheme'] ?? '') !== 'https' ||
empty($parts['host']) || isset($parts['user']) || isset($parts['pass'])) {
fail(400, 'Only HTTPS image URLs are allowed');
}
$host = strtolower(rtrim($parts['host'], '.'));
if (!in_array($host, $allowedHosts, true)) {
fail(403, 'Image host is not allowed');
}
$body = '';
$tooLarge = false;
$ch = curl_init($url);
curl_setopt_array($ch, [
CURLOPT_FOLLOWLOCATION => false,
CURLOPT_CONNECTTIMEOUT => 4,
CURLOPT_TIMEOUT => 10,
CURLOPT_PROTOCOLS => CURLPROTO_HTTPS,
CURLOPT_USERAGENT => 'html2canvas-image-proxy',
CURLOPT_RETURNTRANSFER => false,
CURLOPT_HEADER => false,
CURLOPT_WRITEFUNCTION => function ($handle, string $chunk) use (&$body, &$tooLarge, $maxBytes): int {
if (strlen($body) + strlen($chunk) > $maxBytes) {
$tooLarge = true;
return 0;
}
$body .= $chunk;
return strlen($chunk);
},
]);
$ok = curl_exec($ch);
$status = (int) curl_getinfo($ch, CURLINFO_RESPONSE_CODE);
$contentType = strtolower(trim(explode(';', (string) curl_getinfo($ch, CURLINFO_CONTENT_TYPE))[0]));
$error = curl_error($ch);
curl_close($ch);
if ($tooLarge) {
fail(413, 'Image exceeds size limit');
}
if ($ok === false || $status < 200 || $status >= 300) {
error_log('Image proxy fetch failed: ' . $error);
fail(502, 'Upstream image fetch failed');
}
$finfo = new finfo(FILEINFO_MIME_TYPE);
$detectedType = $finfo->buffer($body);
$allowedTypes = ['image/jpeg', 'image/png', 'image/gif', 'image/webp'];
if (!in_array($contentType, $allowedTypes, true) || $detectedType !== $contentType) {
fail(415, 'Unsupported media type');
}
header('Content-Type: text/plain; charset=utf-8');
echo 'data:' . $contentType . ';base64,' . base64_encode($body);
Replace the example hostnames with your approved image hosts. If an image URL contains a query string, PHP’s $_GET['url'] decoding handles the parameter only if the caller URL-encodes it correctly. Do not add a permissive redirect-following setting without revalidating every redirect target: an allowed host could otherwise redirect the fetch to a prohibited address. A production endpoint should also have request-rate limits and operational logging that does not expose sensitive query strings.
The response format matters. This example returns a plain-text body containing a value such as data:image/png;base64,...; it does not return raw image bytes with an image content type. That distinction follows the documented proxy contract. Error responses are plain text and should be investigated rather than treated as image content.
Point html2canvas at the PHP endpoint and export
Serve proxy.php from the same origin as the page where possible, then pass its path in the proxy option. The library supplies the target image through the proxy request’s url parameter.
What’s actually slowing this PC down?
Pick the symptom - the matching free tool is one click away.
const element = document.querySelector('#capture');
html2canvas(element, {
proxy: '/proxy.php'
}).then(canvas => {
document.body.appendChild(canvas);
const png = canvas.toDataURL('image/png');
// Use `png` as a data URL, or assign it to an image/download flow.
}).catch(error => {
console.error('Canvas rendering failed:', error);
});
Ensure that the selector identifies an element and that the page’s own scripts can reach /proxy.php. For a cross-origin proxy endpoint, its own cross-origin policy may become another obstacle; a same-origin endpoint avoids adding that dependency. Once the promise resolves, the canvas can be displayed or exported with canvas.toDataURL('image/png'), as in the project’s examples.
Rank #4
Test the proxy in a controlled way
- Start with a small, known image on a host you have explicitly allowed. Confirm it is reachable from the PHP server over HTTPS.
- Open the page containing the capture element and check the browser developer console and Network panel for the proxy request. Confirm its query contains an encoded
urlvalue and that the endpoint returns a successful response. - Inspect the response body. It should begin with a data URI prefix such as
data:image/jpeg;base64,, not an HTML error page or raw binary bytes. - Render the element, then call
toDataURL(). A resolved render with an exportable PNG confirms that the browser produced a readable canvas for this case. - Repeat with a representative image from each allowed host and with expected image types and sizes. A single successful image does not establish that every upstream host behaves identically.
Troubleshoot common failures
| Symptom | Likely cause | What to check or change |
|---|---|---|
| External image is absent | The image server did not authorize the page through CORS, or the proxy option was not configured. | Use useCORS: true only when the remote response authorizes the page; otherwise inspect the proxy request and endpoint response. |
Canvas displays but toDataURL() throws a security error |
The canvas was tainted by content that was not loaded with valid CORS permission or through the expected proxy path. | Check each external image in the element, not just the one most visible image. Do not rely on allowTaint as an export fix. |
| PHP returns 400 or 403 | The URL is malformed, not HTTPS, includes credentials, or its host is not in the allowlist. | URL-encode the query parameter, remove user-info from the URL, and add only a verified image host that the application needs. |
| PHP returns 413 | The response exceeded the configured byte limit. | Use an appropriately sized image or deliberately adjust the limit after considering memory and bandwidth costs. |
| PHP returns 415 | The upstream response is not an allowed image type, the server mislabeled it, or the bytes do not match the declared type. | Check the upstream content type and actual file format. Do not simply allow every MIME type. |
| PHP returns 502 | The upstream fetch failed, timed out, redirected, or returned a non-success status. | Check server-side connectivity, URL, TLS and upstream status in logs; the example disables redirects intentionally. |
| Render waits then completes without a slow image | The image may be taking longer than html2canvas’s image timeout or failing upstream. | Check the Network panel and PHP logs. The documented default imageTimeout is 15,000 ms; increase it only when slower image delivery is expected and acceptable. |
Performance, reliability, and cost trade-offs
A proxy adds a fetch from the PHP server, then transfers the result back to the browser in base64 form. That adds a request and a bandwidth path compared with a permitted direct CORS load. Base64 representation also takes more space than the original binary payload, and the PHP process holds the image bytes while it builds the response. The sample’s size cap and timeout limit per-request exposure, but your deployment’s concurrency, memory, outbound network policy and rate controls determine how much traffic it can safely handle.
For repeated captures, consider whether your application can store approved images on its own origin or reuse already-fetched resources. Avoid public caching of private or access-controlled images unless you have designed cache keys, authorization and expiry appropriately. The supplied html2canvas documentation establishes the proxy response contract and configuration defaults; it does not prescribe PHP hosting, caching strategy, or a complete security configuration.
Or skip the browser setup
If the actual goal is to capture a webpage as an image or PDF rather than produce a canvas inside your own page, ScreenshotNeo is a website screenshot API and MCP server. It is not a drop-in html2canvas proxy or a fix for exporting a canvas in your application; it is an alternative way to request a page capture. Its one-call API can return PNG, JPEG, WebP or PDF. See the ScreenshotNeo documentation for request options.
curl -G "https://api.screenshotneo.com/v1/shot" -d access_key=YOUR_API_KEY --data-urlencode url=https://example.com -o shot.webp
ScreenshotNeo removes cookie and consent banners, newsletter popups and chat widgets before capture; each step can be turned off. Bot checks, blank pages and failed loads are not billed, and response headers report the page verdict and billing status. Its MCP server lets AI agents use tools to take screenshots, get page information and capture PDFs. The free plan includes 1,000 screenshots a month with no card; paid plans start at $5 for 3,000 shots. A screenshot API is useful when you need a page capture without maintaining browser capture setup, but it does not replace the PHP image-proxy flow above when your application specifically needs an exportable in-page canvas. Sign up for 1,000 free screenshots a month with no card.
Frequently Asked Questions
Does the PHP proxy change or store the source image?
The example fetches bytes for the request and returns them as a data URI; it contains no code that writes the image to persistent storage. Server logging, caching, and retention can still be affected by the surrounding web server or hosting configuration.
Can the proxy handle authenticated images?
The example does not forward credentials or cookies to an image host. Add authenticated fetching only for a specific trusted use case, and keep secrets server-side rather than accepting credentials from the browser.
Can I use this exact endpoint for arbitrary user-submitted image URLs?
No. The sample is intentionally based on an allowlist. Arbitrary URL fetching creates SSRF and resource-exhaustion risks; use a tightly controlled host policy and network egress restrictions.
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.

