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

Some links on this page are affiliate links: if you buy through them we may earn a commission, at no extra cost to you.

Use useCORS: true when the image server permits your page’s origin; otherwise configure html2canvas with a same-origin PHP proxy. The proxy fetches the image and returns it in the base64 data-URI format html2canvas expects. A PHP proxy is a URL-fetching server endpoint, so it must also restrict which hosts and resources it can access.

Why html2canvas skips images from another domain

html2canvas reconstructs the selected DOM element in a browser canvas; it does not take a screenshot by bypassing the browser’s content-policy rules. When an image comes from a different origin, the browser may prevent it from being read into an exportable canvas. The image can therefore be missing from the result, or the canvas can become tainted and unusable for reading or export.

The two supported approaches are to let the remote image server authorize your site through CORS, or to fetch the image through a proxy you control. The right choice depends chiefly on whether you can configure the image server. Turning on allowTaint does not make a tainted canvas safely exportable; use CORS or a proxy for images you need in the output.

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

Choose direct CORS or a PHP proxy

Approach Use it when What must be true Trade-off
Direct CORS with useCORS: true You control the image server, or its operator permits your page’s origin. The image response must include a suitable Access-Control-Allow-Origin header for the page loading it. No intermediary proxy request is needed, but you depend on the image server’s CORS configuration.
Same-origin proxy with proxy The remote image server does not provide the required CORS permission, and you can operate a server endpoint. Your endpoint must fetch the requested image, validate it, and return the data-URI response html2canvas expects. The proxy adds a server request and bandwidth path, and it creates security and resource-management responsibilities.

The configuration reference gives proxy a default of null and useCORS a default of false. If you leave the proxy unset, html2canvas does not load cross-origin images through that proxy mechanism. Its documented imageTimeout default is 15,000 milliseconds; that timeout is not a substitute for controlling how much work your PHP endpoint permits.

First try direct CORS

When you can configure the image host, this is usually the simpler path: the browser requests the image from that host, and the host authorizes your origin. Set the option on the html2canvas call:

html2canvas(document.querySelector('#capture'), {
  useCORS: true
}).then(canvas => {
  document.body.appendChild(canvas);
});

This setting asks html2canvas to attempt CORS-enabled image loading. It cannot grant permission on the image server’s behalf. If that server does not return an appropriate CORS header, use a proxy instead or arrange for the remote host to allow your origin.

Configure html2canvas to use the PHP proxy

The html2canvas proxy contract is specific: the browser calls an endpoint with a ?url= query parameter, and the endpoint returns the fetched resource as a base64 data URI. Give the PHP endpoint’s same-origin path to the proxy option:

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
html2canvas(document.querySelector('#capture'), {
  proxy: '/proxy.php'
}).then(canvas => {
  document.body.appendChild(canvas);
});

For example, if your page is served from https://example.com, the relative path /proxy.php resolves to that site. The proxy URL must be reachable by the browser running html2canvas. A PHP script on a different, inaccessible host does not become same-origin merely because it can fetch the image.

Build the PHP endpoint

This compact example demonstrates the required request and response shape. It accepts only HTTPS URLs on an explicit host allowlist, disables redirects, checks the upstream status and content type, and caps the response size. Change the example host to a host you intentionally support. It is still a starting point, not a complete security boundary: production deployments should additionally apply network-level egress restrictions and deployment-specific abuse controls.

<?php
declare(strict_types=1);

// Use an explicit allowlist. Replace this example with the image hosts
// your application actually needs to support.
$allowedHosts = ['images.example.com'];
$rawUrl = $_GET['url'] ?? '';

if (!is_string($rawUrl) || $rawUrl === '' || strlen($rawUrl) > 2048) {
    http_response_code(400);
    exit('Invalid URL');
}

$url = filter_var($rawUrl, FILTER_VALIDATE_URL);
$parts = $url ? parse_url($url) : false;
$host = is_array($parts) ? strtolower($parts['host'] ?? '') : '';

if (!$url || !is_array($parts) || strtolower($parts['scheme'] ?? '') !== 'https'
    || $host === '' || !in_array($host, $allowedHosts, true)
    || isset($parts['user']) || isset($parts['pass'])) {
    http_response_code(400);
    exit('URL not allowed');
}

$ch = curl_init($url);
curl_setopt_array($ch, [
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_FOLLOWLOCATION => false,
    CURLOPT_CONNECTTIMEOUT => 5,
    CURLOPT_TIMEOUT => 10,
    CURLOPT_USERAGENT => 'html2canvas-image-proxy',
    CURLOPT_PROTOCOLS => CURLPROTO_HTTPS,
    CURLOPT_REDIR_PROTOCOLS => CURLPROTO_HTTPS,
    CURLOPT_MAXFILESIZE => 5 * 1024 * 1024,
]);
$bytes = 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_errno($ch);
curl_close($ch);

if ($bytes === false || $error !== 0 || $status < 200 || $status >= 300) {
    http_response_code(502);
    exit('Upstream image fetch failed');
}
if (strlen($bytes) > 5 * 1024 * 1024) {
    http_response_code(413);
    exit('Image too large');
}
$allowedTypes = ['image/jpeg', 'image/png', 'image/gif', 'image/webp'];
if (!in_array($contentType, $allowedTypes, true)) {
    http_response_code(415);
    exit('Unsupported media type');
}

header('Content-Type: text/plain; charset=UTF-8');
echo 'data:' . $contentType . ';base64,' . base64_encode($bytes);

The sample uses cURL, so PHP’s cURL extension must be installed and enabled. The 5 MiB cap, 5-second connection timeout, 10-second overall timeout, and host list are example policy choices, not html2canvas requirements. Set limits appropriate to the images and traffic your application is meant to support.

Why URL validation alone is not enough

A public endpoint that fetches any URL supplied by a visitor can be abused to make your server request internal services or consume excessive network, CPU, and memory. FILTER_VALIDATE_URL checks URL syntax; it does not make a destination safe. A scheme check alone is not sufficient either. The example’s exact hostname allowlist narrows the destinations, but DNS and network behavior still need deployment-appropriate safeguards.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
  • Keep the host allowlist narrow. Do not accept arbitrary hostnames merely because they pass URL validation.
  • Do not follow redirects blindly. A permitted public host can redirect to a destination you did not intend to allow.
  • Use outbound network rules to prevent requests to private, loopback, link-local, and otherwise internal address ranges. Consider DNS resolution and rebinding risks when deciding how to enforce destination checks.
  • Limit URL length, connection time, total time, response bytes, and concurrent requests. Apply rate limits or authentication if the endpoint is not intended for public unrestricted use.
  • Accept only image media types the application needs, and consider validating the fetched bytes as well as the upstream content-type header.

The example rejects redirects and allows only four common image response types. Add or remove types deliberately; do not echo arbitrary upstream content. These safeguards are application and deployment guidance, not a complete security recipe prescribed by html2canvas.

Render and export the canvas

Appending the resulting canvas is useful while checking the output. To export it as a PNG data URL after the promise resolves, call toDataURL on the returned canvas:

html2canvas(document.querySelector('#capture'), {
  proxy: '/proxy.php'
}).then(canvas => {
  const pngDataUrl = canvas.toDataURL('image/png');
  const link = document.createElement('a');
  link.href = pngDataUrl;
  link.download = 'capture.png';
  link.click();
});

Export must happen after html2canvas has finished rendering. If an image is absent, check whether the proxy request succeeded and returned the expected data URI; a promise resolving does not by itself prove that every image loaded.

Troubleshoot missing images and failed exports

  • The remote image is missing with useCORS: true: inspect the image response’s CORS headers. The remote server must authorize the page’s origin. If you cannot change that server, use the same-origin proxy approach.
  • The PHP proxy returns “URL not allowed”: confirm the image uses HTTPS and that its exact hostname is in your allowlist. Do not solve this by allowing every host; add only destinations you trust and need.
  • The proxy returns “Upstream image fetch failed”: check whether the host is reachable from the PHP server, whether it returned a successful status, and whether the request exceeded the configured time limits. A browser loading the URL successfully does not establish that your server can reach it.
  • The proxy returns “Unsupported media type”: inspect the upstream response’s content type. It may be returning an HTML error page or a file type the endpoint does not permit. Add a media type only if your application needs it and you validate the response appropriately.
  • The browser reports a proxy or network error: verify the endpoint path, that PHP is executing the script, and that the response is available from the page’s origin. Check the browser’s network panel for the proxy status and response body.
  • The canvas cannot be exported: verify that all included images were loaded through CORS or the proxy rather than allowing a cross-origin image to taint the canvas. allowTaint does not make a tainted canvas readable.
  • Large images are slow or fail: review upstream latency, your endpoint’s time and size limits, and the html2canvas imageTimeout setting. Raising a timeout alone can increase resource use; first determine whether the remote resource is appropriate to fetch.
Independent reader supportYour contribution helps us test, update, and keep practical guides available for everyone.Support on Ko-Fi

Performance, reliability, and cost considerations

Direct CORS avoids the extra proxy hop when the remote server already permits your origin. A proxy adds a request through your PHP server, which must transfer and encode the image; base64 encoding also increases the response representation’s size compared with the raw bytes. Keep the proxy close to the application’s intended workload, bound concurrency and response sizes, and avoid turning a rendering endpoint into an unmetered general-purpose download service.

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

Reliability depends on both the remote image host and your proxy: either can be slow or unavailable. A client-side timeout and a server-side timeout govern different parts of the path. The documented html2canvas default image timeout is 15 seconds, while the example PHP limits are shorter; tune both only after deciding how long a user-facing capture should wait and what load your server can safely handle. Cache only if your application can safely account for image freshness and access rules.

Or skip the browser setup

If your goal is a screenshot of a webpage rather than a canvas assembled from a particular DOM element, ScreenshotNeo can capture the URL with one API request. It is a website screenshot API and MCP server from Yorker Media, not an html2canvas PHP proxy; use html2canvas when you specifically need to render a selected element in the page.

For the API options and setup, see the ScreenshotNeo documentation.

curl -G "https://api.screenshotneo.com/v1/shot" -d access_key=YOUR_API_KEY --data-urlencode url=https://stripe.com -o shot.webp
  • Cookie/consent banners, newsletter popups, and chat widgets are removed before the shot; each of those steps can be turned off.
  • Bot checks, blank pages, failed loads, timeouts, and cache hits are not billed; response headers report the page verdict and billing status.
  • 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.

Sign up for ScreenshotNeo’s free plan to get 1,000 screenshots a month with no card.

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

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.