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.

Short answer: Chrome is refusing to expose the rules of a stylesheet that html-to-image is inspecting. This is normally an origin-access restriction, not invalid CSS. Identify the stylesheet named in the error, test the page from a local HTTP server instead of file://, and make any stylesheet your application controls readable from the page’s origin. If the failing access is only needed for web-font discovery, use the version-supported fontEmbedCSS option or deliberately set skipFonts instead.

What the SecurityError means

The browser’s CSS Object Model (CSSOM) protects stylesheet rules that the calling document is not allowed to inspect. Reading sheet.cssRules can therefore throw SecurityError: Failed to read the 'cssRules' property from 'CSSStyleSheet': Cannot access rules. Chrome 64 made this failure visible in cases that had appeared to work before. It is a security boundary: changing JavaScript in your application cannot make an inaccessible third-party sheet readable.

html-to-image does more than copy the styles directly attached to your target node. Its conversion pipeline clones the node, computes and copies styles, discovers @font-face rules, fetches font files, and serializes the result for rendering. During font discovery it may inspect every stylesheet in document.styleSheets. A Google Fonts sheet, an embedded widget, an extension-injected sheet, or another unrelated resource can therefore be the sheet that fails.

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

1. Find the stylesheet that is actually failing

  1. Read the complete console entry and stack. Record the URL shown for the stylesheet, whether the URL is null, and the first html-to-image function in the stack.
  2. Check the installed package version. Options and internal behavior differ between releases. Inspect the package’s README and TypeScript declarations that are installed in your project rather than copying an option from an issue or blog post.
  3. List the page’s stylesheets in DevTools. In the console, run Array.from(document.styleSheets).map(s => s.href). Compare the results with the URL in the exception. A sheet loaded by a component you did not write can still be the trigger.
  4. Check how the page was loaded. A page opened directly from disk has a file:// origin and is not equivalent to the same files served over HTTP. Also note redirects, subdomains, ports, and protocol changes: each can affect origin checks.

Do not assume the stylesheet visually associated with the captured element is responsible. The error identifies the access that failed; diagnose that URL first.

2. Reproduce from a local development server

If you double-clicked an HTML file, stop testing that way. Start the project’s normal development server (for example, the command documented by your framework), then open its http://localhost URL. A minimal static test can be served with any local HTTP server available on your machine, such as your framework’s built-in server. Reload the page and run the same capture.

This distinction matters because Chrome’s local-file security behavior can prevent CSSOM access that works from an HTTP development origin. A historical Stack Overflow explanation of the Chrome 64 change summarized the practical fix as using a local development server for functionality that depends on readable CSSOM rules. If the error disappears over HTTP, keep the server-based workflow; do not weaken browser security to preserve file:// testing.

3. Restore access when your application controls the stylesheet

Prefer same-origin delivery

Serve the CSS from the same scheme, host, and port as the page whenever practical. This avoids a cross-origin CSSOM boundary and is usually the least surprising deployment model. Verify the final URL after redirects; a same-site-looking URL can still end on another origin.

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

Configure CORS on the stylesheet response

If the CSS must come from another origin, configure that host to allow the requesting origin and ensure the stylesheet request is made in a mode compatible with that policy. Inspect the actual CSS response in DevTools, including redirects, rather than adding a header to an unrelated API or image endpoint. The relevant response is the stylesheet that appears in the exception.

A response might include an origin-specific header such as Access-Control-Allow-Origin: https://app.example.test. Use the real origin of your page, keep credentials rules consistent with your request, and send the header on every redirect target and cached variant. Your server or CDN configuration determines whether this is possible; client-side JavaScript cannot grant access after the response has arrived.

Check loading mode and caching

Confirm that the link or fetch configuration and the server’s CORS policy agree. A cached response without the required header can make the problem appear intermittent, so purge or revalidate the CSS resource after changing headers. Test in a clean profile if an extension may be injecting a stylesheet.

4. Decide whether the failure is font discovery

In html-to-image, a stylesheet access error often occurs while the library searches for @font-face rules. You have two different choices, and they are not interchangeable:

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.
Goal Approach Effect on output
Keep automatic font discovery Fix same-origin delivery or the stylesheet’s CORS policy. Fonts can be discovered and embedded normally.
Use known font CSS Pass the version-supported fontEmbedCSS option with CSS you supply. The project also documents getFontEmbedCSS() for obtaining reusable embed CSS. Discovery/parsing is replaced by your CSS; you must keep it accurate.
Do not embed fonts Set the documented skipFonts option. The browser may use fallback fonts, changing glyphs, wrapping, and text metrics.

Verify that your installed html-to-image version exposes these options before shipping code. After setting fontEmbedCSS or skipFonts, compare the image with a normal browser rendering, especially headings, icon fonts, and text near line breaks.

Example with explicit font CSS

The exact call shape depends on your installed version, but a current API may look like this:

const css = await getFontEmbedCSS(node);
const dataUrl = await toPng(node, { fontEmbedCSS: css });

Treat that as a pattern, not a guarantee for every release: inspect the package declarations and adapt the function import and call signature to the version you installed.

Example that deliberately skips fonts

const dataUrl = await toPng(node, { skipFonts: true });

Use this only when fallback typography is acceptable. It bypasses font downloads and embedding; it does not repair stylesheet access.

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

5. About the historical guard patch

An answer to the original Chrome 64 report suggested guarding stylesheet-rule access with a presence check. That can prevent a failure when a property is genuinely absent, but it is not a universal cross-origin fix. A stylesheet can expose a cssRules property whose getter still throws for an origin-inaccessible sheet.

If you must patch a pinned dependency, catch the access exception around the individual stylesheet and skip that sheet, then document that fonts or other rules from it may be omitted. Keep the patch small, lock the package version, and cover it with an image comparison test. Prefer an upstream release or configuration option when available.

Choose the least disruptive remedy

  • The sheet is yours and its fonts matter: serve it same-origin or configure CORS on that CSS response.
  • The sheet is yours but you know the required font CSS: provide fontEmbedCSS and avoid discovery.
  • The sheet is third-party and its fonts are unnecessary: use skipFonts or a controlled skip path, then inspect typography.
  • The page was opened from disk: move the test to the project’s HTTP development server before changing application code.
  • The sheet comes from an extension or widget: reproduce without the extension or isolate the widget; do not assume your application’s CORS settings can change it.

Common symptoms and fixes

The error names a Google Fonts or other font-provider URL

Font discovery is probably traversing that external sheet. Either make the font CSS readable under a policy you control, supply explicit embed CSS, or skip font embedding and verify the fallback result.

The URL is null

The sheet may be an inline stylesheet, a data URL, a document-created sheet, or an injected resource. Inspect the sheet list and stack rather than applying a URL-based CORS rule blindly.

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

CORS headers were added but nothing changed

Check the exact stylesheet response, redirects, protocol/port, request mode, and cache. A header on an API, image, or font endpoint does not automatically make the CSSOM readable.

It works in one browser or profile

Compare the loaded stylesheet URLs and extensions. An extension can inject a sheet that another profile does not have. Also test a production-like HTTP origin, not a local file.

Skipping fonts fixes the exception but the image looks wrong

That is expected when the original font was required. Restore access or provide explicit font CSS instead of accepting changed metrics.

Someone recommends disabling web security

Do not use that as a normal fix. It weakens browser protections, hides deployment errors, and does not help users running your application normally.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
Independent reader supportYour contribution helps us test, update, and keep practical guides available for everyone.Support on Ko-Fi

Performance and reliability considerations

Font discovery can add network requests and make captures sensitive to third-party availability. Supplying reusable embed CSS can make the conversion path more predictable, while skipping fonts reduces work at the cost of typography. Same-origin assets also avoid cross-origin surprises during future stylesheet changes. Whichever path you choose, test with slow networks, blocked font requests, redirects, and a clean browser profile; record the html-to-image version alongside your capture tests.

Or skip the browser setup

If your goal is a rendered image or PDF rather than debugging a browser capture, 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 turned off. Bot checks or CAPTCHAs, blank pages, timeouts, failed loads, and cache hits are not billed, and the response reports the result in X-Page-Verdict and X-Billed headers.

One GET request returns PNG, JPEG, WebP, or PDF:

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,
)
r.raise_for_status()
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}`);
if (!res.ok) throw new Error(`${res.status} ${res.statusText}`);
const fs = await import('node:fs/promises');
await fs.writeFile('shot.webp', Buffer.from(await res.arrayBuffer()));

See the ScreenshotNeo API documentation for the 63 capture options, including full-page and selector captures, device presets, custom CSS and JavaScript, waits, request blocking, cookies and headers, PDF settings, caching, signed links, asynchronous jobs, bulk capture, and usage reporting. Its MCP server exposes take_screenshot, get_page_info, and capture_pdf to 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. Create a free ScreenshotNeo account.

Frequently Asked Questions

Is this a CSS syntax error?

Usually not. The exception is raised when the browser blocks CSSOM rule access for a stylesheet whose origin or loading context is not readable by the page.

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

Will adding CORS to my font files fix it?

Not necessarily. The stylesheet response named in the stack must be readable; headers on a separate font or API endpoint do not grant access to that stylesheet’s cssRules.

Can I use the old property-existence patch safely?

Only as a reviewed, version-pinned workaround. A property check may still reach a getter that throws, and skipping the sheet can remove required fonts.

Why does the same code work after deployment but fail locally?

A deployed HTTP origin and a page opened with file:// have different security behavior. Reproduce through your local development server before changing the capture code.

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.

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