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

If html2canvas omits an SVG, the cause is usually one of four things: the SVG has not finished loading, the request is cross-origin without permission, the SVG data URI is malformed, or the capture exceeds browser canvas limits. Check the browser console and Network panel first, then apply the fix that matches the failing request. useCORS: true helps only when the image server sends a suitable Access-Control-Allow-Origin header; it cannot grant permission by itself.

Diagnose the failure in one minute

  1. Open DevTools before starting the capture. In Console, note CORS, decode, security and image-load errors. In Network, locate the SVG request, its final URL, status, redirect chain and response headers.
  2. Inspect the element being captured. The SVG may be an <img>, a CSS background-image, an inline <svg>, or an SVG referenced by <use>. Test each resource independently in a new tab.
  3. Confirm loading has completed before calling html2canvas. A visible placeholder does not prove that the image has decoded.
  4. Compare origins, including redirects. A page on app.example.com and an asset on cdn.example.com are different origins. A same-origin URL that redirects to a CDN must be treated as cross-origin too.
  5. Run a small capture with an onError callback. This distinguishes a failed SVG resource from a blank or oversized canvas.

Why html2canvas skips an SVG

Cross-origin content is the most common cause

Browsers protect canvases from reading pixels loaded from another origin without permission. html2canvas does not bypass that policy. Its default allowTaint value is false, so resources that would taint the canvas may be skipped. The remote server must send Access-Control-Allow-Origin, or your application must fetch the asset through a same-origin proxy.

The image request is still in progress

html2canvas can start while an SVG is still downloading, parsing or decoding. This is especially common after inserting an image dynamically, changing its src, or opening a page whose images load lazily.

The data URI is not safely encoded

Raw SVG markup contains characters such as #, spaces, quotes and angle brackets that can break a data URI. Percent-encode the complete markup with encodeURIComponent before assigning data:image/svg+xml,....

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

A redirect changes the origin

Issue #3020 describes a local URL that redirects to a CDN. The initial URL can look same-origin to the library, so the CORS request strategy is not applied to the final resource. Always inspect the final URL in Network, not only the URL in your HTML.

Nested SVG resources have their own rules

Inlining the outer SVG does not automatically make referenced images, fonts, stylesheets, filters or <use> targets safe. Every nested request must either be same-origin, CORS-enabled, or embedded in a canvas-safe form.

Fix a remote SVG with CORS

Use this option when you control the asset server or it already returns a permissive CORS header. The header must be on the SVG response itself (and on any nested resources), not only on the HTML page.

const target = document.querySelector('#capture');

const canvas = await html2canvas(target, {
  useCORS: true,
  onError: error => {
    console.warn('html2canvas resource failed:', error.message);
  }
});
document.querySelector('#result').src = canvas.toDataURL('image/png');

For a public asset, the response commonly includes Access-Control-Allow-Origin: *. For a credentialed request, the server must name the requesting origin and configure credentials consistently; do not combine a wildcard origin with credentials. If you cannot change the asset server, use the proxy method below. Setting allowTaint: true is not a CORS fix: a tainted canvas cannot be safely exported with toDataURL or toBlob.

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

Use a same-origin proxy when CORS is unavailable

A proxy fetches the SVG on your server and serves it from the same origin as the page. html2canvas’s getting-started guidance describes a proxy that returns the fetched image as a base64 data URI. Keep the endpoint restricted: validate allowed schemes and hosts, limit response size, enforce timeouts, and block private-network addresses to avoid creating a server-side request forgery risk.

const svgUrl = 'https://cdn.example.com/icons/chart.svg';
const proxyUrl = '/image-proxy?url=' + encodeURIComponent(svgUrl);

const img = document.querySelector('#chart-icon');
img.src = proxyUrl;
await img.decode();

const canvas = await html2canvas(document.querySelector('#capture'), {
  proxy: proxyUrl,
  onError: error => console.warn('Proxy resource failed:', error.message)
});

The proxy response must have the correct image content type and be reachable from the browser without another cross-origin hop. If your proxy returns a data URI, assign that URI to the image before capture. A proxy does not repair malformed SVG markup or missing nested resources; those still need to be fixed at the source.

Encode inline SVG correctly

Embedding a small SVG removes the outer network request. Encode the entire string, then wait for the browser to decode the image.

const svg = `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100">
  <circle cx="50" cy="50" r="40" fill="tomato"/>
</svg>`;

const icon = document.querySelector('#icon');
icon.src = 'data:image/svg+xml;charset=utf-8,' + encodeURIComponent(svg);
await icon.decode();

const canvas = await html2canvas(document.querySelector('#capture'));

Do not use an unescaped string such as data:image/svg+xml,<svg ...>. If the SVG contains an external bitmap, webfont, stylesheet, filter or <use> reference, make that dependency inline or provide the required same-origin/CORS response as well. Inline encoding is convenient for stable icons; a normal URL is easier to cache and update.

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

Wait for every image before capturing

Waiting for the DOM alone is insufficient. This helper waits for successful loads and reports failures, including images used as CSS backgrounds when you check those separately.

async function waitForImages(root) {
  const images = [...root.querySelectorAll('img')];
  await Promise.all(images.map(async image => {
    if (image.complete && image.naturalWidth > 0) return;
    await new Promise((resolve, reject) => {
      image.addEventListener('load', resolve, { once: true });
      image.addEventListener('error', () => reject(new Error(`Failed: ${image.src}`)), { once: true });
    });
    if (image.decode) await image.decode().catch(() => {});
  }));
}

const target = document.querySelector('#capture');
await waitForImages(target);
const canvas = await html2canvas(target, {
  useCORS: true,
  imageTimeout: 15000,
  onError: error => console.warn(error)
});

imageTimeout controls how long html2canvas waits for an image. Increase it only for genuinely slow assets; a large timeout can make a broken URL appear to hang. Lazy-loaded images may require scrolling them into view or triggering the page’s own loading logic before this helper runs.

Investigate redirects explicitly

Use the Network panel to see the final request. You can also inspect a URL with a manual fetch where the browser permits it:

const response = await fetch(svgUrl, { redirect: 'manual' });
console.log({
  type: response.type,
  status: response.status,
  location: response.headers.get('location')
});

An opaque response or a blocked redirect is a sign that browser policy is involved, not that html2canvas failed to parse the SVG. Serve the final asset with CORS, point the page directly at a CORS-enabled URL, or route it through your same-origin proxy.

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

Handle CSS backgrounds, inline SVG and complex content

CSS background images

Find the computed background-image value in DevTools. Apply the same CORS or proxy solution to that URL. Because the image is not represented by an <img>, an image-only waiting helper will not detect it; wait for the stylesheet and resource explicitly.

Inline SVG and <use>

An inline SVG normally avoids an outer image request, but an external symbol sheet or external paint resource reintroduces a network-origin dependency. Copy the required symbols into the document or make the referenced file canvas-safe.

foreignObjectRendering

Try foreignObjectRendering: true only for a targeted experiment when browser-supported HTML inside SVG is the problem:

const canvas = await html2canvas(target, {
  foreignObjectRendering: true,
  useCORS: true,
  onError: error => console.warn(error.message)
});

This is an alternate rendering path, false by default. It does not bypass CORS and can behave differently between browsers. Return to the normal renderer if it introduces missing styles or inconsistent output.

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

Fix blank or truncated captures caused by size

If the SVG is present but the entire image is blank or cut off, set the capture viewport from the element’s scroll dimensions:

const el = document.querySelector('#capture');
const canvas = await html2canvas(el, {
  windowWidth: el.scrollWidth,
  windowHeight: el.scrollHeight
});

The html2canvas FAQ gives a rough current maximum dimension of about 32,767 pixels for Chrome/Chromium, Firefox and desktop Safari. Actual limits vary by browser, GPU, operating system, device memory and iOS. Split very tall pages into sections, reduce scale, or capture a smaller element when approaching those limits.

Choose the right fix

Symptom Likely cause Best first action
Only remote SVGs are missing No CORS header or cross-origin redirect Enable useCORS with a server header, or use a same-origin proxy
Inline SVG is missing Malformed or unencoded data URI Use encodeURIComponent and wait for decode()
Intermittent results Capture starts before image decoding or lazy loading Wait for image loads, then capture
Console reports a failed resource 404, blocked request, invalid SVG or nested dependency Open the exact request and fix its status, content type or dependencies
Whole result is blank or partial Canvas dimension or memory limit Set dimensions explicitly and split or reduce the capture
Complex browser HTML is missing Normal renderer’s partial CSS support Test foreignObjectRendering, then compare browsers

Troubleshooting checklist for stubborn cases

  • Verify the response status is successful and the content type matches an SVG.
  • Check that the SVG has a valid root element and dimensions through viewBox, width or height.
  • Search the SVG text for external URLs in images, fonts, CSS, filters and <use>.
  • Check the final URL after every redirect; a CDN hop changes the CORS decision.
  • Confirm the server’s CORS header is present on the image response, not only on an OPTIONS response.
  • Use onError and record the browser, html2canvas version, URL and response headers.
  • Reduce the test case to one SVG and one target element. If that still fails after origin and encoding fixes, file a minimal issue with those details.

Performance and reliability considerations

Large SVGs, filters, embedded fonts and full-page captures consume substantial browser memory. Reuse a loaded image, avoid repeatedly converting the same markup to data URIs, and capture only the required element. A short, deliberate wait is more reliable than an arbitrary multi-second delay. Cache stable assets at your origin or through a controlled proxy, but do not cache personalized or authorization-bearing responses publicly.

html2canvas is browser-side and implements only the CSS and SVG behavior its renderer supports. It is not a universal SVG engine. If a reduced example fails in one browser but works in another, record both browser versions and keep a browser-specific fallback rather than assuming the output is deterministic.

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

Or skip the browser setup

For server-side screenshots, ScreenshotNeo accepts one GET request and returns PNG, JPEG, WebP or PDF. Before capture it accepts cookie or consent banners and removes more than 60 known consent platforms, newsletter popups and chat widgets; each cleanup step can be disabled. Bot checks or 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.

One-call examples

See the parameter reference in 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
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)
const q = new URLSearchParams({ access_key: 'YOUR_API_KEY', url: 'https://stripe.com' });
const res = await fetch(`https://api.screenshotneo.com/v1/shot?${q}`);

ScreenshotNeo includes full-page captures with lazy images loaded, CSS-selector element capture, dark mode, 12 device presets plus custom viewports, retina scale, custom CSS and JavaScript, clicks before capture, selector hiding, selector/delay/network-idle waits, request and resource blocking, custom headers, cookies, user agents and Authorization, timezone and geolocation, transparent backgrounds, resizing, configurable cache TTLs, signed image links, asynchronous jobs with signed webhooks, bulk capture for up to 100 URLs per call, a usage API and an OpenAPI specification. Its 63 options include PDF paper size, margins, landscape mode and page ranges. An MCP server provides take_screenshot, get_page_info and capture_pdf for Claude, Cursor and other MCP clients.

Plan Included shots Price
Free 1,000 per month $0, no card
Starter 3,000 $5
Growth 15,000 $15
Pro 60,000 $39
Scale 250,000 $99
Business 1,000,000 $249

Yearly billing provides two months free, and every feature is available on every plan. Start with 1,000 free screenshots a month—no card required.

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

FAQ

Does useCORS: true add the missing response header?

No. It tells html2canvas to request images with CORS. The image server still has to grant permission with its response headers.

Can I solve the problem by setting allowTaint: true?

That may allow a resource to be drawn, but the resulting canvas is tainted and cannot be exported safely. It is not a replacement for CORS or a proxy.

Why does a screenshot work in one browser but not another?

SVG, CSS, canvas-size and foreignObject behavior depends on the browser engine and device limits. Compare a reduced test case and record versions before choosing a fallback.

When should I use a server-side screenshot instead?

Use one when browser security, repeatable rendering or unattended jobs make client-side capture impractical. ScreenshotNeo provides server-side PNG, JPEG, WebP and PDF responses plus asynchronous and MCP workflows.

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

Frequently Asked Questions

Does useCORS: true add the missing response header?

No. It changes the request strategy; the image server must send the permission header.

Can allowTaint: true solve the issue?

It can produce a tainted canvas that cannot be exported reliably, so use CORS or a proxy instead.

Why can two browsers produce different SVG results?

Browser engines and device canvas limits differ, especially for foreignObject content and very large captures.

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.