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

Set crossOrigin on the image before assigning its src; setting it on the canvas does not grant permission. Then configure the image server to return an Access-Control-Allow-Origin header matching your page. With both sides configured, you can draw a cross-origin image and read or export its pixels. Without successful CORS, the browser taints the canvas and protected read/export methods fail with SecurityError.

The correct pattern

Use the image element’s CORS mode before the network request starts. In markup:

<img crossorigin="anonymous" src="https://cdn.example.com/photo.jpg" alt="">

In JavaScript, assign crossOrigin first, attach handlers, and only then set src:

const img = new Image();
img.crossOrigin = "anonymous";

img.onload = () => {
  const canvas = document.querySelector("canvas");
  const ctx = canvas.getContext("2d");
  ctx.drawImage(img, 0, 0);

  const pixels = ctx.getImageData(0, 0, canvas.width, canvas.height);
  console.log(pixels.data.length);
};

img.onerror = (event) => {
  console.error("Image failed to load", event);
};

img.src = "https://cdn.example.com/photo.jpg";

The order matters. Changing crossOrigin after src has started a request cannot change that request; create or reload the image instead.

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

What crossOrigin controls

HTMLImageElement.crossOrigin is a string property that reflects the image’s crossorigin content attribute. Its meaningful states are anonymous and use-credentials.

anonymous: cross-origin without credentials

This requests the image with CORS while omitting cookies and other credentials for a cross-origin load. It is the appropriate default when the image is public and does not require a logged-in session.

const img = new Image();
img.crossOrigin = "anonymous";
img.src = "https://images.example.com/public/banner.webp";

use-credentials: include credentials

This uses a credentialed CORS request. Cookies, client certificates, and authorization credentials may be included. The server must explicitly allow credentials with Access-Control-Allow-Credentials: true and return an origin-specific Access-Control-Allow-Origin value.

const privateImage = new Image();
privateImage.crossOrigin = "use-credentials";
privateImage.onload = () => draw(privateImage);
privateImage.src = "https://account.example.com/avatar.jpg";

A wildcard response such as Access-Control-Allow-Origin: * is incompatible with credentialed sharing. Use the requesting origin instead, and configure the server to vary the response by origin when necessary.

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

Missing, empty, or invalid values

If the attribute is missing, the image uses the No CORS state. An empty or invalid value uses the anonymous state. Do not rely on an empty attribute as a substitute for deliberately setting anonymous; being explicit makes the request and server configuration easier to audit.

Server headers are required

The browser-side property does not grant access by itself. The image response must opt in with an appropriate Access-Control-Allow-Origin header.

Public image example

For a page at https://app.example.com, a response can include:

Access-Control-Allow-Origin: https://app.example.com

For a genuinely public, non-credentialed asset, a wildcard may be suitable:

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
Access-Control-Allow-Origin: *

Do not use the wildcard when the image is loaded with use-credentials.

Credentialed image example

A credentialed response needs both an origin-specific allow header and:

Access-Control-Allow-Origin: https://app.example.com
Access-Control-Allow-Credentials: true

The server must send these headers on the actual image response, including responses served through a CDN or object-storage layer. A header on an HTML page, redirect, or unrelated API response does not authorize the image.

Why a canvas becomes tainted

When a foreign-origin image is drawn without successful CORS approval, the canvas becomes origin-tainted. This protects pixels that the page is not authorized to read. A tainted canvas can still be displayed, but script cannot extract its protected pixel data.

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

Operations blocked by tainting

  • getImageData()
  • toDataURL()
  • toBlob()
  • captureStream()

These calls throw a SecurityError (or otherwise fail according to the API) once the canvas contains unauthorized foreign-origin data. Drawing a same-origin image generally avoids this path. If any tainted source is drawn, later clearing or drawing another image does not make the existing canvas trustworthy; use a new canvas and redraw only approved sources.

Markup, JavaScript, and canvas examples

Markup-only image

<canvas id="preview" width="800" height="450"></canvas>
<img id="hero" crossorigin="anonymous"
     src="https://cdn.example.com/hero.jpg" alt="Product preview">
<script>
  const canvas = document.getElementById("preview");
  const ctx = canvas.getContext("2d");
  const hero = document.getElementById("hero");
  hero.addEventListener("load", () => ctx.drawImage(hero, 0, 0));
  hero.addEventListener("error", () => console.error("CORS image load failed"));
</script>

Exporting a permitted image

const canvas = document.querySelector("canvas");
const ctx = canvas.getContext("2d");
const img = new Image();
img.crossOrigin = "anonymous";
img.onload = () => {
  canvas.width = img.naturalWidth;
  canvas.height = img.naturalHeight;
  ctx.drawImage(img, 0, 0);

  try {
    const dataUrl = canvas.toDataURL("image/png");
    document.querySelector("#result").src = dataUrl;
  } catch (error) {
    if (error.name === "SecurityError") {
      console.error("Canvas is tainted; check image CORS headers", error);
    } else {
      throw error;
    }
  }
};
img.src = "https://cdn.example.com/photo.png";

Drawing a selected region

function drawCrop(url, sx, sy, sw, sh) {
  return new Promise((resolve, reject) => {
    const image = new Image();
    image.crossOrigin = "anonymous";
    image.onload = () => {
      const canvas = document.createElement("canvas");
      canvas.width = sw;
      canvas.height = sh;
      canvas.getContext("2d").drawImage(image, sx, sy, sw, sh, 0, 0, sw, sh);
      resolve(canvas);
    };
    image.onerror = reject;
    image.src = url;
  });
}

A reliable debugging checklist

  1. Verify ordering. Confirm img.crossOrigin or the markup attribute appears before src is assigned.
  2. Inspect the actual response. In browser developer tools, open Network, select the image request, and check the response headers for Access-Control-Allow-Origin.
  3. Match the origin exactly. Scheme, hostname, and port all matter. https://app.example.com is different from http://app.example.com and from another port.
  4. Check credentials. For use-credentials, require Access-Control-Allow-Credentials: true and an origin-specific allow header, not a wildcard.
  5. Reload after changing settings. Create a new Image or assign src again after changing the mode; a completed request cannot be retrofitted.
  6. Test the read operation. A visible image does not prove that pixels are readable. Call getImageData() or an export method and inspect the exact exception.
  7. Check redirects and CDNs. Every redirect and the final image response must preserve a compatible CORS policy. Configure the cache to vary by Origin when responses differ per origin.

Common errors and fixes

“The image displays, but toDataURL throws SecurityError”

The image loaded without an acceptable CORS response or another source already tainted the canvas. Add the correct image-server header, set the mode before src, reload, and draw onto a fresh canvas.

“No ‘Access-Control-Allow-Origin’ header”

The server did not authorize your page. This cannot be fixed solely in front-end JavaScript. Change the CDN or object-storage CORS configuration, proxy the asset through a server you control, or use a same-origin copy where you have the rights to do so.

“Credential is not supported if the CORS header is ‘*’”

You used use-credentials while the response used a wildcard origin. Return the exact requesting origin and Access-Control-Allow-Credentials: true, or switch to anonymous if credentials are unnecessary.

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

“Setting crossOrigin did nothing”

Usually the property was set after src, the image was cached from an earlier mode, or the server header is absent. Set the property first, create a new image, and inspect the network response rather than relying on the visual result.

Canvas is tainted after drawing several images

One unauthorized image is enough. Audit every drawImage source, including canvases, video frames, SVG images, and composited assets. Ensure each foreign-origin source is CORS-authorized before it is drawn.

Choosing the right mode

Situation Image setting Server requirement Typical result
Public image; pixels must be readable anonymous Matching Access-Control-Allow-Origin Canvas can be read if all sources pass
Private image requiring cookies or credentials use-credentials Specific origin plus Access-Control-Allow-Credentials: true Credentialed CORS load and readable pixels
Image does not need pixel access Omit CORS mode No CORS authorization needed for display alone May display, but drawing can taint the canvas
Same-origin image Either explicit mode or default, as appropriate Same-origin policy applies Normally avoids cross-origin tainting
Independent reader supportYour contribution helps us test, update, and keep practical guides available for everyone.Support on Ko-Fi

Performance and operational considerations

CORS does not change the image’s intrinsic size or decoding cost. Large images still consume memory when decoded and copied into a canvas. Set the canvas dimensions deliberately, draw at the required resolution, and avoid repeatedly creating large temporary canvases. If you control the CDN, configure cache behavior carefully: an origin-reflecting policy generally requires the cache to vary on Origin so one site’s authorization is not served to another.

Use anonymous whenever credentials are not needed. It reduces exposure of cookies and avoids the stricter credentialed-header requirements. Use use-credentials only when the image service genuinely depends on an authenticated session.

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

Or skip the browser setup

If your goal is to obtain a clean screenshot rather than manipulate browser pixels yourself, ScreenshotNeo provides a website screenshot API and MCP server. It accepts consent banners before capture 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 responses identify the page verdict and billing status in X-Page-Verdict and X-Billed headers.

One request returns PNG, JPEG, WebP, or PDF. The API supports full-page captures with lazy images loaded, CSS-selector element captures, dark mode, device presets and custom viewports, retina scale, PDF paper and page controls, custom CSS and JavaScript, clicks, selector or network-idle waits, request blocking, headers, cookies, user agents, authorization, timezone, geolocation, transparent backgrounds, resizing, configurable cache TTLs, signed links, asynchronous jobs with signed webhooks, bulk capture of up to 100 URLs per call, usage reporting, and an OpenAPI specification. Parameter names used by other screenshot APIs also work, easing migration.

cURL

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

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)

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}`);

See the ScreenshotNeo API documentation for parameters and response details. The Free plan includes 1,000 screenshots per month with no card; paid plans start at $5 for 3,000 shots. An MCP server exposes take_screenshot, get_page_info, and capture_pdf to Claude, Cursor, and other MCP clients. Create a free ScreenshotNeo account to start.

Frequently Asked Questions

Can I set crossOrigin on a canvas element?

No. Set the CORS mode on each HTMLImageElement before its request begins; the canvas has no setting that authorizes a foreign image.

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.

Does crossOrigin fix CORS when I do not control the image server?

No. The server must return a compatible Access-Control-Allow-Origin response. If it cannot, use a permitted same-origin or server-side copy instead.

Which mode should I use for a public CDN image?

Use anonymous when you need readable canvas pixels and the CDN can authorize your page. Use use-credentials only when authentication is required.

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.