The API

2. Deploy to Netlify

Generate the folder, create the site, set the variables.

The second half. This produces the URL your App Proxy will point at.

Netlify is the documented path and the one that ships configured. If you'd rather host this elsewhere, the requirements are small and the setup shape is identical — see Other platforms.

What actually gets deployed

Not the theme. npm run api:build writes a folder that has nothing to do with your storefront:

deploy/netlify/
├── netlify.toml config
├── package.json no dependencies, on purpose
├── public/.gitkeep an empty folder Netlify insists on
└── netlify/functions/
├── admin-gql.mjs → POST /admin.gql
├── service.mjs → POST /service
└── _src/ the handler code, copied in

It is self-contained. Nothing in it imports back into the theme, so it can be deployed on its own, committed to its own repo, or dragged into a dashboard.

Two things about it surprise people, and both are correct:

  • There is no build step. The functions are plain JavaScript, already runnable. netlify.toml declares a no-op build command on purpose.
  • The published site is empty. This is an API, not a website. Netlify requires a publish directory, so it gets one with nothing in it.

1. Generate the folder

npm run api:build

Run this from the theme root, not from deploy/. It prints the routes it made and the access scopes your operations need — keep that output, you'll want the scopes in a minute.

2. Create the Netlify site

cd deploy/netlify
npx netlify login
npx netlify init

netlify init asks a few questions. The answers that matter:

  • "What would you like to do?"Create & configure a new project
  • Team → whichever you use
  • Project name → anything, or leave blank for a generated one. This becomes your URL, so acme-store-api beats stalwart-tulumba-a58f6c.

If it offers to set a build command, it guessed wrong — say no, or ignore it. Netlify's framework detection walks up out of this folder, finds the theme's Vite setup, and suggests something like remix vite:build. That fails with remix: command not found, because none of that is installed here.

netlify.toml states the real command explicitly, and file config beats anything saved in the UI — so even if a wrong guess got saved, the next deploy overrides it.

3. Deploy

npx netlify deploy --prod

You'll see it bundle two functions and upload one file. That one file is the .gitkeep. Correct.

Note the URL it prints — something like https://acme-store-api.netlify.app. That is what goes in your App Proxy.

4. Set the environment variables

Three of them. These are what make the API work, and it will fail closed without them.

npx netlify env:set SHOPIFY_SHOP your-store.myshopify.com
npx netlify env:set SHOPIFY_CLIENT_ID your_client_id
npx netlify env:set SHOPIFY_API_SECRET your_client_secret
VariableWhere it comes from
SHOPIFY_SHOPYour .myshopify.com domain — not a custom domain
SHOPIFY_CLIENT_IDclient_id in shopify.app.toml
SHOPIFY_API_SECRETThe Client secret from the Dev Dashboard

Then deploy again:

npx netlify deploy --prod

This is not optional. A Netlify deploy captures the environment as it was at deploy time, so variables set after a deploy are invisible to the functions until a new one goes out.

The dashboard equivalent is Site configuration → Environment variables.

Never set SHOPIFY_ALLOW_RAW_GQL here. It makes the endpoint accept arbitrary GraphQL from anyone who can reach it, which on a storefront means everyone.

5. Check it responded

curl -i -X POST 'https://your-site.netlify.app/admin.gql' \
-H 'Content-Type: application/json' \
-d '{"operation":"GetShop"}'

You want a 401.

ResponseMeaning
401 Invalid signature✅ Working. The endpoint is up and refusing unsigned traffic
500 Server is not configuredSHOPIFY_API_SECRET isn't set, or you didn't redeploy after setting it
404Wrong path. The routes are /admin.gql and /service at the site root

A 401 looks like failure and is the healthy answer. Your storefront's requests arrive through the App Proxy with a signature attached; a raw curl has none, so it is correctly rejected.

Where the functions are in the dashboard

Under the Functions tab, not in the deploy's file browser. The file browser shows the published folder, which is empty apart from netlify.toml — that is expected and not a sign anything went wrong.

Redeploying later

Operations and services are compiled into this folder at build time. Adding a .graphql file to the theme does nothing in production until you rebuild and redeploy:

npm run api:build
cd deploy/netlify && npx netlify deploy --prod

api:build regenerates the files but leaves .netlify/ alone, so the folder stays linked to your site. You run netlify init once, ever.

Next

Wire the two halves together → Connect them