Skip to main content

How to resolve CORS issues?

When loading images into the Fabric canvas, Ydesign defaults every image's crossOrigin attribute to "anonymous" — the prerequisite for exporting the canvas without taint. If the image host doesn't return the matching CORS headers, you'll see the classic error:

Access to image at 'http://example.com/image.jpg' from origin 'http://your-domain.com' has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource.

Once this happens the image fails to load; even if it does, the canvas becomes "tainted" and subsequent store.toDataURL() / store.toBlob() / saveAsImage() calls all throw.


① Serve images with CORS headers (the real fix)

Add Access-Control-Allow-Origin: * to the image response.

AWS S3 — Bucket → Permissions → CORS:

[
{
"AllowedOrigins": ["*"],
"AllowedMethods": ["GET", "HEAD"],
"AllowedHeaders": ["*"],
"MaxAgeSeconds": 3000
}
]

Details: AWS CORS docs.

Aliyun OSS / Tencent COS / Cloudflare R2 — each has a CORS rule panel; same fields (Origin *, Methods GET, HEAD).

Self-hosted Nginx:

location ~* \.(png|jpg|jpeg|webp|svg|gif)$ {
add_header Access-Control-Allow-Origin *;
add_header Access-Control-Allow-Methods 'GET, HEAD';
}

Some CDNs strip upstream CORS headers — enable "pass-through CORS" at the CDN layer too.


② Add crossOrigin to every <img> tag

Browsers cache each image once. If your page elsewhere (a preview, a wizard step, etc.) rendered the same URL without crossOrigin, the canvas will reuse that "tainted" cache and crossOrigin='anonymous' becomes useless.

// ❌ Wrong
<img src="http://example.com/image.jpg" />

// ✅ Right
<img src="http://example.com/image.jpg" crossOrigin="anonymous" />

The built-in <ImagesGrid /> already sets crossOrigin="anonymous" by default — no extra work needed when you reuse it.


③ Diagnose which image is broken

When store.toDataURL() throws a tainted canvas error, iterate over images to find the culprit:

const urls = store.objects
.filter(o => o.type === 'Image')
.map(o => o.src);

for (const url of urls) {
try {
const res = await fetch(url, { mode: 'cors' });
const hasCors = !!res.headers.get('access-control-allow-origin');
console.log(hasCors ? '✅' : '❌', url);
} catch (e) {
console.log('❌', url, e.message);
}
}

Fix the server CORS for any URL marked ❌.


④ Fallback: proxy through your backend

If you absolutely can't configure CORS on a third-party origin, proxy through your own backend:

// Express
app.get('/proxy/image', async (req, res) => {
const upstream = await fetch(req.query.url);
const buffer = Buffer.from(await upstream.arrayBuffer());
res.setHeader('Access-Control-Allow-Origin', '*');
res.setHeader('Content-Type', upstream.headers.get('content-type') || 'image/jpeg');
res.send(buffer);
});

Then use the proxied URL:

store.addElement({
type: 'Image',
src: `/proxy/image?url=${encodeURIComponent('https://third-party.com/image.jpg')}`,
});

⚠️ This adds backend load + bandwidth cost — use sparingly.


See also