CORS 403 Errors When Loading Sanity Images into Canvas with crossOrigin
I can see you're getting CORS 403 errors specifically when using crossOrigin = 'anonymous' with Sanity image URLs in canvas/WebGL contexts. This is a frustrating issue, especially since you've been using this successfully for years.
Understanding Canvas and CORS Requirements
When you set crossOrigin = 'anonymous' on an image element before drawing it to a canvas or using it in WebGL, the browser makes a CORS-enabled request. This is required to prevent "tainting" the canvas with cross-origin data. The browser needs to see proper CORS headers (Access-Control-Allow-Origin) in the response from the image server, or it will block the image from being used in canvas contexts.
What We Know About Sanity's CORS Configuration
Based on the CORS Origins documentation, the CORS origins you configure in manage.sanity.io (Settings → API → CORS Origins) control which domains can access your Sanity Data API with credentials.
However, there's an important documented limitation: according to a community answer about Asset CDN CORS control, the CORS settings configured in the Management Dashboard "do not automatically apply to the Asset CDN." The Asset CDN documentation does mention that it handles "CORS (Cross-Origin Resource Sharing) configuration for cross-domain asset access," but the specific behavior isn't something you can directly configure through the dashboard settings.
This means the Asset CDN at cdn.sanity.io (where your images are served from) has its own CORS configuration separate from the user-facing CORS settings in your project dashboard.
Diagnostic Steps
First, gather information from your browser's Network tab when the failing image request occurs:
- Check the Response Headers - Look for
Access-Control-Allow-Origin. Is it present? What's its value? - Look for Preflight Requests - Is there an OPTIONS request before the image request? Does it succeed or fail?
- Examine the 403 Response - What's in the response body? Any error messages?
- Verify Request Headers - Confirm the
Originheader is being sent correctly - Test Different Browsers - Does this happen in Chrome, Firefox, and Safari?
Things to Try
1. Test with Image Transformations
Try using @sanity/image-url to build URLs with transformation parameters:
import imageUrlBuilder from '@sanity/image-url'
const builder = imageUrlBuilder({
projectId: 'your-project-id',
dataset: 'your-dataset'
})
const imageUrl = builder
.image(yourImageAsset)
.auto('format')
.url()Sometimes adding transformation parameters like ?auto=format or ?w=1920 can affect how the CDN processes the request.
2. Verify Your Image URLs
Make sure you're using the correct URL format from Sanity's Asset CDN. The URL should look like:
https://cdn.sanity.io/images/{projectId}/{dataset}/{assetId}-{dimensions}.{format}3. Test Without Transformations
Try accessing the raw asset URL directly to see if the issue is specific to transformed images or affects all asset requests.
4. Check Your CORS Configuration
Even though Asset CDN CORS isn't controlled by the dashboard settings, verify your Data API CORS settings are correct in manage.sanity.io under Settings → API → CORS Origins. Add your production domain if it's not there already.
You Should Contact Sanity Support
Given that:
- This worked for years and recently broke
- You're getting a 403 (Forbidden) rather than a standard CORS error
- The Asset CDN's CORS behavior isn't directly configurable through the dashboard
- Your excellent CodeSandbox reproduction clearly demonstrates the issue
This appears to be something that requires investigation from Sanity's infrastructure team. Reach out through:
- Sanity Community Slack in the #help channel
- Email support@sanity.io
When contacting support, include:
- Your project ID
- The CodeSandbox reproduction link
- Network tab screenshots showing the 403 response and all headers
- When this issue started occurring
- Whether it affects all images or only specific ones
- The diagnostic information from above
Temporary Workaround (If Needed)
While waiting for support, if you absolutely need a solution for production, you could proxy images through your own server that adds CORS headers:
// Server-side proxy
app.get('/image-proxy', async (req, res) => {
const imageUrl = req.query.url;
const response = await fetch(imageUrl);
const buffer = await response.arrayBuffer();
res.set('Access-Control-Allow-Origin', '*');
res.set('Content-Type', response.headers.get('content-type'));
res.send(Buffer.from(buffer));
});This bypasses Sanity's global CDN and adds latency, so it should only be temporary while the underlying issue is resolved.
The combination of a 403 error, the fact this worked previously, and the timing of the issue suggests something changed at the infrastructure level. Your reproduction case will help Sanity's team diagnose and resolve this quickly.
Show original thread10 replies
Sanity – Build the way you think, not the way your CMS thinks
Sanity is the developer-first content operating system that gives you complete control. Schema-as-code, GROQ queries, and real-time APIs mean no more workarounds or waiting for deployments. Free to start, scale as you grow.