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

Fix a Laravel/Vue html2canvas export by first finding the server that delivers the missing image. If that server is yours, return an Access-Control-Allow-Origin header for the requesting page and set useCORS: true. If it is a CDN or third-party host, Laravel cannot change its policy; configure that host or fetch the image through a tightly restricted same-origin proxy. A canvas that still fails after CORS is correct may be hitting browser canvas limits or unsupported CSS rather than a CORS problem.

What the error actually means

html2canvas does not capture the browser’s pixels like an operating-system screenshot. It walks the DOM and recreates supported styles, images and elements on a new canvas. Consequently, the result can differ from what is visible on screen.

When an image comes from another origin without CORS approval, drawing it onto a canvas taints that canvas. Browser security then blocks pixel-reading operations such as toBlob() and toDataURL(), usually with a SecurityError. html2canvas defaults allowTaint to false, so it normally skips unsafe images instead of bypassing browser policy.

1. Find the server causing the failure

  1. Open DevTools and reproduce the export.
  2. In Console and Network, locate every failed or missing image request. Record the final URL after redirects, status code, and response headers.
  3. Compare the image origin (scheme, host and port) with the origin of the Vue page. A Laravel API and a separate image CDN are different origins even when they share a company domain.
  4. Check whether the response contains Access-Control-Allow-Origin matching the page origin. A wildcard is not suitable for credentialed requests.

Do not start by changing Laravel configuration unless the failing URL is actually served by Laravel. The browser evaluates the headers on the image response, not on an unrelated API response.

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

2. Direct CORS when you control the image host

Return an appropriate response header

Configure the server that serves the image to return Access-Control-Allow-Origin: https://your-vue.example (use your real production origin). If requests include cookies or authorization, also return Access-Control-Allow-Credentials: true and a specific origin, never *. Add Vary: Origin when responses vary by origin and ensure redirects preserve the policy.

Laravel 12.x configuration

Laravel 12.x automatically handles CORS OPTIONS requests through the globally included HandleCors middleware. To customize allowed origins, methods and headers, publish the configuration file:

php artisan config:publish cors

Edit the published config/cors.php for the routes Laravel serves. Typical values include your Vue origin in allowed_origins, the required HTTP methods in allowed_methods, and headers such as Content-Type and Authorization. Clear cached configuration after changes with php artisan config:clear (or rebuild your deployment cache). This only affects responses generated by this Laravel application; it cannot add headers to a third-party CDN.

3. Enable CORS loading in Vue

Use a component ref and set useCORS: true. This tells html2canvas to request images in CORS mode; it does not grant permission on the remote server.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
import { ref } from 'vue'
import html2canvas from 'html2canvas'

const exportArea = ref(null)

async function exportImage() {
  const canvas = await html2canvas(exportArea.value, {
    useCORS: true,
    onError: (error) => console.warn('html2canvas resource failed:', error.message)
  })

  const blob = await new Promise((resolve) =>
    canvas.toBlob(resolve, 'image/png')
  )
  if (!blob) throw new Error('Canvas export returned no image data')

  const url = URL.createObjectURL(blob)
  const link = document.createElement('a')
  link.href = url
  link.download = 'export.png'
  link.click()
  URL.revokeObjectURL(url)
}

Use await nextTick() before capture if Vue has just rendered data, and wait for images to finish loading when the page inserts them asynchronously. A failed image may be omitted while the rest of the export succeeds; inspect the warning and Network panel rather than assuming the entire canvas is broken.

4. Use a same-origin proxy when the image host cannot change

html2canvas supports a proxy option that loads a remote image through your page’s origin. Your proxy fetches the destination server-side, validates the response, and returns the bytes with an appropriate content type. Keep it a security boundary:

  • Allow-list destination hosts and, where possible, path prefixes.
  • Accept only https URLs and reject private, loopback and link-local IP ranges to prevent SSRF.
  • Limit response size, MIME types, redirects, timeout and concurrency.
  • Do not forward arbitrary incoming headers or credentials.
  • Cache approved assets and log the destination and result for abuse investigation.

Point html2canvas’s proxy option at this endpoint. A proxy is a fallback, not a way to expose an unrestricted URL-fetch service.

5. Distinguish CORS from other export failures

Images are missing but export downloads

The image request was blocked, redirected without CORS, returned an unexpected content type, or loaded after capture began. Fix the image response, use a controlled proxy, or wait for the image before calling html2canvas.

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

toBlob() or toDataURL() throws SecurityError

Some cross-origin content was drawn without CORS approval and tainted the canvas. Find every image, including CSS background images and images inside components, and correct each response. Setting allowTaint: true does not make export safe; it permits drawing while leaving the canvas unreadable.

The canvas is blank or only partly rendered

Very large dimensions can exceed browser canvas width, height or area limits, which vary by browser and platform. Capture a smaller region, reduce scale, split a long document into pages, or use PDF/server-side capture. Unsupported CSS can also produce visual differences because html2canvas implements only selected CSS properties.

The console shows a preflight failure

For requests that trigger preflight, the image server must answer OPTIONS with allowed origin, method and requested headers. Verify that the preflight is reaching the intended host and that redirects are not sending it elsewhere.

Choosing the right remedy

Situation Best path Reason
You control the image server Direct CORS headers plus useCORS: true Fewest moving parts and no proxy bandwidth.
Image is on a vendor CDN Ask the vendor for CORS support or use a restricted proxy Laravel cannot alter another origin’s response.
Images are approved but export is huge Reduce dimensions/scale or split captures Canvas limits are separate from CORS.
You need a server-generated screenshot Use a screenshot API A browser service can capture without shipping DOM reconstruction to the client.

Troubleshooting checklist

  • Wrong origin: follow redirects and configure the final image host.
  • Header mismatch: make the value exactly match the Vue origin, including scheme and port.
  • Credentials: use a specific origin with Access-Control-Allow-Credentials: true.
  • Cached old headers: purge CDN caches and verify the response in a private window.
  • Lazy images: scroll or otherwise trigger loading, then await img.decode() where supported.
  • Proxy errors: inspect allow-list, DNS/IP validation, timeout and response-size logs.
  • CSS mismatch: simplify unsupported filters, blend modes, pseudo-elements or complex fonts.
  • Blank export: capture a smaller element to determine whether dimensions are the cause.

Performance, reliability and cost considerations

Client-side html2canvas consumes the user’s CPU and memory. Full-page, high-retina captures increase work roughly with pixel area; capture only the required element, choose a sensible scale, and avoid repeatedly exporting in a reactive render loop. For long pages, split by logical sections and combine the resulting files.

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

Direct CORS avoids proxy transfer costs. A proxy adds server bandwidth, latency and an attack surface, so enforce the limits above. If a capture is business-critical, report failures to the user and retain the original URL and browser error for diagnosis rather than silently producing an incomplete image.

Independent reader supportYour contribution helps us test, update, and keep practical guides available for everyone.Support on Ko-Fi

Or skip the browser setup

ScreenshotNeo is the first alternative to try when you need a clean server-side website screenshot: cookie banners, newsletter popups and chat widgets are removed before capture, and only clean shots are billed. Bot checks, blank pages, timeouts, failed loads and cache hits are not billed; each response identifies the result with X-Page-Verdict and X-Billed headers. It also provides an MCP server for Claude, Cursor and other MCP clients, with take_screenshot, get_page_info and capture_pdf tools.

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)
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 complete option list and request details in the ScreenshotNeo documentation. Features include full-page lazy-image capture, CSS-selector elements, device presets, custom viewport and retina scale, PDF page controls, custom CSS/JavaScript, clicks, waits, blocked resources, headers, cookies, authorization, geolocation, caching, signed links, asynchronous webhooks, bulk capture and a usage API.

The Free plan includes 1,000 screenshots per month with no card. Paid plans start at $5 for 3,000 shots; all features are included on every plan. Create a free ScreenshotNeo account.

Free tools Windows power users keep installed

One-click scans. No signup required.

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

FAQ

Can I fix a third-party image by adding CORS in Laravel?

No. The response must come from the server that supplied the image, or from a proxy you operate.

Does useCORS make an image public?

No. It only asks the browser to use CORS when fetching; the remote response still has to grant the requesting origin.

Why does the page look correct while the export is wrong?

Normal display can show an image without allowing canvas pixel reads. html2canvas additionally reconstructs supported DOM and CSS, so CORS, canvas limits and unsupported styles can each change the result.

Frequently Asked Questions

Can I fix a third-party image by adding CORS in Laravel?

No. The response must come from the server that supplied the image, or from a proxy you operate.

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

Does useCORS make an image public?

No. It only asks the browser to use CORS; the remote response still has to grant the requesting origin.

Why does the page look correct while the export is wrong?

Browser display, canvas security and html2canvas’s DOM/CSS reconstruction have different rules, so a visible page can still produce a missing or incomplete export.