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
- 👉 Upload panel — upload local files to your own OSS (with proper CORS)
- 👉 Cloud Render API — image / font URL CORS requirements
- 👉 Editor Configuration · custom upload —
setUploadFunc