CORS Block on Bulk Operation Result URL

Summary

The Bulk Operation result URL (GCS signed URL) returned by the Shopify Admin API is CORS-blocked and cannot be fetched from the
browser on some shops
. Even with the same app, same code, and same query, the behavior differs from shop to shop.

Reproduction Environment

  • App: Shopify embedded apps (confirmed on multiple apps)
  • Fetch method: Direct GET request (axios/XHR) from the frontend against the url field of the bulk operation
  • Target: The bulk operation’s JSONL result file

Observed Bucket Assignment Differences

Shop Result Bucket GoogleAccessId Browser Fetch
capital-castle-demo bulk-operation-intermediate-outputs-us-central1 shopify-core-tiers@shopify-tiers.iam.gserviceaccount.com :cross_mark: CORS blocked
capital-castle-demo-3 shopify-tiers-assets-prod-us-east1 assets-us-prod@shopify-tiers.iam.gserviceaccount.com :white_check_mark: Success
cc-testdemo-2 shopify-tiers-assets-prod-us-east1 assets-us-prod@shopify-tiers.iam.gserviceaccount.com :white_check_mark: Success
  • Failing shops always use the bulk-operation-intermediate-outputs-us-central1 bucket
  • Succeeding shops always use the shopify-tiers-assets-prod-us-east1 bucket
  • A given shop consistently returns the same bucket (verified over multiple runs)

Browser Behavior on Failure

Access to XMLHttpRequest at ‘https://storage.googleapis.com/bulk-operation-intermediate-outputs-us-central1/…’
from origin ‘’ has been blocked by CORS policy:
No ‘Access-Control-Allow-Origin’ header is present on the requested resource.

Failed to load resource: net::ERR_FAILED

Results of hitting both buckets with curl and an Origin header:

  • Both buckets return 200
  • intermediate-outputs-us-central1 returns no Access-Control-Allow-* headers
  • shopify-tiers-assets-prod-us-east1 returns CORS headers

Historical Behavior

On capital-castle-demo, the fetch used to succeed (250 previously retrieved order records remain cached on the page). At some
point it began failing.
Shopify may have changed the bucket assignment on the server side.

Hypotheses Ruled Out by Testing

  • :cross_mark: Frontend domain difference
  • :cross_mark: App dev vs prod build
  • :cross_mark: Partners Dev Store vs regular store (all tested shops are Partners Dev Stores)
  • :cross_mark: Duties and Taxes feature preview being enabled (both failing and succeeding shops have it enabled)

Requests

  1. Add CORS headers to the bulk-operation-intermediate-outputs-us-central1 bucket so its behavior matches shopify-tiers-assets-prod-us-east1.
  2. Alternatively, document the behavior where different shops receive different bulk result buckets (no such documentation
    currently exists).
  3. Alternatively, unify the pipeline so all shops return the same (CORS-enabled) bucket.

Relevant Official Documentation

Neither document mentions CORS constraints on the bulk operation url, nor that the returned bucket may differ between shops.

Also facing this issue on our apps.

Hi @azy and @Charlie_Tyler,

I can confirm that the CORS header removal was done as part of a greater change to improve the network infrastructure with new regional GCS buckets.

Though the errors you’re receiving with Embedded Apps requesting the Bulk Operation results directly in the front end where not expected.

This change was rolled out incrementally, which explains why some stores were experiencing it and some where not, though it was rolled out to 100% of stores in the last couple hours here.

I’ve discussed this with our developers further, and at this time we have rolled back the changes, while our developers continue to work on this further to prevent the errors from occurring. So you should no longer see the errors, and should be receiving the previously expected CORS headers in the Bulk Operation result requests.

Thanks for the quick rollback, @Kellan-Shopify-san, — really appreciate the transparency.
I confirmed our direct browser fetch of the bulk result URL works as before.
As a safety net we’ve also shipped a thin backend proxy so the frontend no longer depends on CORS headers on that bucket. Happy to keep it in place, but it would be great if the long-term fix preserves the current CORS contract so existing embedded apps don’t need to migrate.

Just to confirm — is fetching the bulk operation result data directly from the frontend still a supported pattern going forward? We’d prefer to keep the direct-from-browser path as the primary approach, since routing large JSONL payloads through our own server is wasteful in terms of server resources and bandwidth when the browser can pull them straight from GCS.

Hi @azy,

I followed up with our developers on this, and they confirmed that you will still be able to request the Bulk Operation results via the Front End client, no need for any App Proxies and Back End server workarounds.

Hi @Kellan-Shopify -san
Well noted! I would like to retrieve the JSONL using direct fetch from the frontend.

Thank you for your kind follow-up. It allows me to work on development with peace of mind.