GuidesSHOPIFY
Call external APIs from a Shopify theme
Theme JavaScript runs on your storefront domain, so Shopify's own endpoints work: /products.json and the Storefront API even answer Access-Control-Allow-Origin: *, while third-party APIs without CORS headers fail. Shopify's server-side route is an app proxy, which needs an app with the write_app_proxy scope and a server behind it. Without one, public GET requests work through https://proxy.cors.dev/ (free, about 600 requests a minute per IP); POST or Authorization needs cors.dev Pro at $5 a month for 500,000 requests.
Where theme code runs
Theme JavaScript in theme.liquid, sections, snippets or asset files runs in the shopper's browser on your storefront domain: your primary custom domain, or https://yourstore.myshopify.com. Every fetch() from the theme carries that origin in the Origin header.
Liquid renders on Shopify's servers, but it has no tag or filter that makes outbound HTTP requests. A theme cannot call a third-party API server-side on its own, so external requests happen in the browser and must pass its CORS check.
Shopify endpoints that need no proxy
Shopify's own storefront endpoints work from theme code without a proxy. On 28 October 2026 we called Shopify's Dawn demo store (https://theme-dawn-demo.myshopify.com) with a foreign Origin header. /products.json?limit=1 answered 200 with Access-Control-Allow-Origin: * (10,196 bytes), and the Ajax API's /products/<handle>.js did the same.
/cart.js and /search/suggest.json answered 200 with no CORS header. That is fine, because theme code calls them on its own origin. The Storefront API at /api/2026-10/graphql.json answered a tokenless POST with 200 and Access-Control-Allow-Origin: *, and its OPTIONS preflight answered 200.
| Endpoint | Access-Control-Allow-Origin | From theme code | Needs cors.dev |
|---|---|---|---|
| /products.json | * | Works | No |
| /products/handle.js | * | Works | No |
| /cart.js | None, same origin only | Works | No |
| /search/suggest.json | None, same origin only | Works | No |
| Storefront API graphql.json | * | Works | No |
| Yahoo Finance chart API | None | Blocked | Yes, free tier |
| ECB euro rates XML | None | Blocked | Yes, free tier |
/products.json is not documented on shopify.dev. The documented options for theme code are:
- Ajax API:
/cart.js,/cart/add.js,/products/<handle>.js,/search/suggest.jsonand/recommendations/products.json, unauthenticated and returning JSON. - Storefront API: GraphQL over
POST, with a public access token in theX-Shopify-Storefront-Access-Tokenheader or tokenless access.
Call a third-party API from a theme
Third-party APIs usually send no CORS header. On 28 October 2026 Yahoo Finance's chart API (https://query1.finance.yahoo.com/v8/finance/chart/GC=F?range=1d&interval=1d, 1,257 bytes) and the ECB daily euro rates XML (https://www.ecb.europa.eu/stats/eurofxref/eurofxref-daily.xml, 1,547 bytes) returned no Access-Control-Allow-Origin, so theme code cannot read either.
Put https://proxy.cors.dev/ in front of the full target URL. The free tier needs no signup and no key, and both URLs answered 200 through it with Access-Control-Allow-Origin echoing https://theme-dawn-demo.myshopify.com.
<p id="gold-price">Loading gold price...</p>
<script>
(async () => {
const output = document.getElementById('gold-price');
const target = 'https://query1.finance.yahoo.com/v8/finance/chart/GC=F?range=1d&interval=1d';
try {
const response = await fetch('https://proxy.cors.dev/' + target);
if (!response.ok) {
throw new Error(response.headers.get('X-Cors-Error') || 'HTTP ' + response.status);
}
const { chart } = await response.json();
const { regularMarketPrice, currency } = chart.result[0].meta;
output.textContent =
'Gold futures: ' +
regularMarketPrice.toLocaleString('en-US', { style: 'currency', currency }) +
' per troy ounce';
} catch (error) {
output.textContent = 'Gold price unavailable: ' + error.message;
}
})();
</script>The snippet reads chart.result[0].meta.regularMarketPrice and meta.currency from the Yahoo response. On 28 October 2026 it printed "Gold futures: $4,068.40 per troy ounce".
The free tier's rules for theme code:
- Methods:
GETandHEADonly;POSTanswers405method_not_allowed. - Rate: About 600 requests per minute per IP and no daily or monthly quota; past that,
429with aRetry-Afterheader that JavaScript can read. - Headers:
Authorization, and names containingtoken,apikey,api-key,api_key,secretorpasswordor ending in-key, answer400header_not_allowed. Other custom headers, including names containingcsrforxsrfsuch asX-CSRF-Token, are dropped and listed inX-Cors-Dropped-Headers. - Errors: When the proxy refuses a request, the
X-Cors-Errorresponse header names the reason.
The Shopify way: an app proxy
An app proxy is Shopify's server-side route. Shopify forwards a storefront URL such as https://yourstore.com/apps/gold-price to a URL on your app's server. The theme calls a path on its own domain, so the browser treats the request as same-origin and CORS never applies.
- Add the scope and the route
Request the
write_app_proxyaccess scope and add an[app_proxy]block toshopify.app.tomlwithurl,prefix(a,apps,communityortools) andsubpath(up to 30 characters, notadmin,services,passwordorlogin). - Map the path
With the settings below,
https://<shop>/apps/gold-priceproxies to/api/gold-priceon your app'sapplication_url. - Verify on the server
Shopify strips the
Cookieheader from app proxy requests andSet-Cookiefrom responses, and adds asignatureparameter your server can check.
# shopify.app.toml: https://<shop>/apps/gold-price is forwarded to <application_url>/api/gold-price
[access_scopes]
scopes = "write_app_proxy"
[app_proxy]
url = "/api/gold-price"
prefix = "apps"
subpath = "gold-price"Changes to prefix and subpath apply only to new installations. An app proxy needs an installed app and a server you run, which also makes it the right home for secret API keys. cors.dev fits when you have neither and the data is public.
Origins to register for cors.dev Pro
cors.dev Pro adds POST, PUT, PATCH and DELETE with bodies up to 1 MiB, plus Authorization, Content-Type and the custom headers you configure, and it can cache a GET for up to 300 seconds when the upstream response allows it, though cache hits still count as requests. It costs $5 a month for 500,000 requests, with a 7-day trial of 1,000 requests and no card, and allows 3,000 requests a minute per account and 100 in flight. The 6 MiB response cap and 30 second upstream deadline stay the same.
You sign up with Google, create a Connection with up to 10 exact page origins and up to 20 target hosts, and send its publishable key in an X-Cors-Key header. Register each origin shoppers or developers load:
- Primary domain: For example
https://www.yourstore.com. - myshopify.com:
https://yourstore.myshopify.com, if it serves the storefront. - Local development:
http://127.0.0.1:9292, whichshopify theme devserves by default.
Shared preview links open on a separate shopifypreview.com origin. Those origins change, so test previews on the free tier. A request from an unlisted origin answers 403 origin_not_allowed.
Good questions.
Can Liquid call an external API on the server?
No. Liquid renders on Shopify's servers but has no tag or filter that makes outbound HTTP requests. Call the API from theme JavaScript, through a proxy if it sends no CORS header, or route it through a Shopify app proxy to a server you run.
Is /products.json an official Shopify API?
It is not documented on shopify.dev, although /products.json?limit=1 on the Dawn demo store answered 200 with Access-Control-Allow-Origin: * on 28 October 2026. The documented options for theme code are the Ajax API, such as /products/<handle>.js and /cart.js, and the GraphQL Storefront API.
Does the Storefront API need a CORS proxy?
No. On 28 October 2026 the OPTIONS preflight and a tokenless POST to /api/2026-10/graphql.json on the Dawn demo store both answered 200 with Access-Control-Allow-Origin: *. Call it directly from theme JavaScript with tokenless access or a public token in X-Shopify-Storefront-Access-Token.
Which origin does a Shopify theme preview use?
It depends on how you open it. shopify theme dev serves http://127.0.0.1:9292 by default, and shared preview links use a separate shopifypreview.com origin. For any other preview, read the Origin request header in the browser network panel.
Can I put a private API key in theme JavaScript with cors.dev?
No. cors.dev never hides an API key: query-string keys, headers sent through cors.dev Pro and the publishable X-Cors-Key are all visible to shoppers. Keep a secret key on your own server behind a Shopify app proxy.