Cloud storage CORS errors during uploads (S3 and GCS)
:::info
Category: Environment & Configuration Issues
Applies to: Direct browser uploads using pre-signed URLs for Amazon S3 or Google Cloud Storage (GCS)
Quick Detection: Signed URL generation succeeds, but the browser blocks the upload request with a preflight CORS error mentioning missing Access-Control-Allow-Origin.
:::
Symptoms
- Upload starts but fails before the first chunk or part completes
- Browser console shows an error similar to:
Access to fetch at '<signed-storage-url>' from origin '<cms-origin>' has been blocked by CORS policy:
Response to preflight request doesn't pass access control check:
No 'Access-Control-Allow-Origin' header is present on the requested resource.
- In Network tab, the preflight (
OPTIONS) or upload request to the storage domain fails - API call that returns the signed URL succeeds, but upload to bucket fails
- Same issue can occur with both S3 and GCS
Likely Causes
- Bucket CORS policy does not include the CMS origin (for example
https://onecms.staging.viewlift.com) - Allowed methods are missing the upload method (
PUTand sometimesPOST) - Allowed request headers are too restrictive for uploader/browser headers
- Environment domains (dev/staging/prod) were not all added to bucket CORS
- CORS policy was updated recently and changes have not propagated yet
What to Check
- Confirm the failing request host
- If URL points to storage (
s3.amazonaws.com,*.s3.*.amazonaws.com,storage.googleapis.com), this is bucket CORS, not backendALLOWED_ORIGINS.
- If URL points to storage (
- Inspect preflight response in DevTools
- Open Network and select failing
OPTIONSrequest. - Check response headers for
Access-Control-Allow-Origin,Access-Control-Allow-Methods, andAccess-Control-Allow-Headers.
- Open Network and select failing
- Verify bucket CORS configuration
- Compare exact CMS origin and upload method/header set with bucket rules.
- Confirm the target environment origin is present.
- Validate signed URL state
- Ensure URL is not expired and host points to expected bucket/region.
- Expired URLs cause different errors, but can be confused with CORS during retries.
How to Fix
Apply bucket-level CORS rules for each CMS environment origin.
Amazon S3 example
[
{
"AllowedOrigins": ["https://onecms.staging.viewlift.com"],
"AllowedMethods": ["GET", "HEAD", "PUT", "POST"],
"AllowedHeaders": ["*"],
"ExposeHeaders": ["ETag", "x-amz-request-id", "x-amz-id-2"],
"MaxAgeSeconds": 3000
}
]
Google Cloud Storage example
[
{
"origin": ["https://onecms.staging.viewlift.com"],
"method": ["GET", "HEAD", "PUT", "POST"],
"responseHeader": ["Content-Type", "ETag", "x-goog-resumable"],
"maxAgeSeconds": 3600
}
]
After updating CORS:
- Apply configuration to the target bucket.
- Wait for propagation (can take a few minutes).
- Retry upload from browser with DevTools open.
- Confirm preflight/upload responses contain expected
Access-Control-Allow-*headers.
Prevention Tips
- Maintain a per-environment origin matrix and update it for every new CMS domain
- Add storage bucket CORS verification (S3 and/or GCS) to deployment checklists
- Prefer explicit environment origins in production over broad wildcards
- Add a post-deploy smoke test for one signed browser upload path
Related Issues
- Backend API CORS looks correct, but direct storage upload still fails
- Upload works in non-browser tools but fails in browser (non-browser clients do not enforce CORS)
- Multipart upload fails on first part with preflight errors
ℹ️ Note
This is usually a storage-layer configuration issue and is separate from management serviceALLOWED_ORIGINSsettings.