Checking my understanding of a 'headless' app (novice starting out with graphQL)

Hello all. I’ve been a REST API used in the past and now have to build a tool to query my new store using graphQL.

I am making progress on the query structure and language (using the Shopify GraphiQL app) but I think I got sidetracked with the idea of having to create a full custom app which seems to complicate things significantly for me (I am NOT an ‘App’ developer as such).

I’ve now installed the ‘Headless’ app which I think I understand allows me to query information via graphQL without the need for a full custom app and seems like a more direct replacement for the REST access I used to have implemented in a previous store (ages ago)

I want to sense check that assumption with the community before I get sidetracked again.

My plan would be to define a headless app and query via curl for the simple requirement that I have (looking up products and variants for basic information and SKUs, barcodes). Once that is up and running I should be able to tinker with querying the correct data using something like curl.

Have I got this correct ?

I am asking also because I was a little alarmed by the notes in the Storefront API permissions section.

“unauthenticated_read_product_listings” etc.

Am I reading that correctly. ‘unauthenticated’ as in literally anyone can query OR would someone still need the Public access token and / or Private access token details before they can query?

I understand that the details I need to query like products and variants is already largely available via the stores public web pages but still wanted to ask.

Can I also assume that a headless app wouldn’t allow me to update a product using the same CURL method? I don’t think I need that today, but I certainly used to use REST to do that from time to time.

Any pointers / comments welcomed.

Thanks

Dave

Hi @Dave_Renwick! You’re on the right track thinking about this, but I’d steer you towards creating an app via the Dev Dashboard rather than using the Headless channel.

The Headless channel gives you Storefront API access, which is buyer-focused and read-only for catalog data. It also requires products to be published to that channel to be queryable. Since you mentioned potentially wanting to update products later (like you did with REST), Storefront API can’t help - it doesn’t support write operations on products.

For a curl-based workflow querying products/variants for SKUs and barcodes, here’s an approach:

  1. Go to dev.shopify.com/dashboard and create an app

  2. In the Versions tab, set App URL to https://shopify.dev/apps/default-app-home, uncheck “Embed app in Shopify admin”, add the read_products scope (plus write_products if you want updates later), and release

  3. Install to your store from the app’s Home tab

  4. Grab your Client ID and Secret from Settings

Then get an access token:

curl -X POST \
  "https://your-store.myshopify.com/admin/oauth/access_token" \
  -H "Content-Type: application/x-www-form-urlencoded" \
  -d "grant_type=client_credentials" \
  -d "client_id=YOUR_CLIENT_ID" \
  -d "client_secret=YOUR_CLIENT_SECRET"

Use that token to query:

curl -X POST \
  https://your-store.myshopify.com/admin/api/2025-01/graphql.json \
  -H "Content-Type: application/json" \
  -H "X-Shopify-Access-Token: YOUR_ACCESS_TOKEN" \
  -d '{"query": "{ products(first: 10) { nodes { id title variants(first: 10) { nodes { sku barcode } } } } }"}'

Tokens from client credentials expire after 24 hours, so you’ll need to refresh by calling the token endpoint again.

If you ever need Storefront API access from this same app (for buyer-facing features), you can create a storefront access token via the Admin API. Add unauthenticated_read_product_listings (or other unauthenticated scopes) to your app, then call:

mutation {
  storefrontAccessTokenCreate(input: { title: "My Storefront Token" }) {
    storefrontAccessToken { accessToken }
    userErrors { field message }
  }
}

That gives you a token for the Storefront API endpoint at https://your-store.myshopify.com/api/2025-01/graphql.json.

To your question about “unauthenticated” scopes - that term means the buyer doesn’t need to authenticate, not that the API is completely open. You still need an access token, but it’s designed to be safe for client-side exposure (unlike Admin API tokens which must stay server-side).

The client credentials guide and Dev Dashboard docs cover the setup in more detail. Let me know if I can clarify any of the above for you!

Thank you Donal.

After I did my post I went back and keep plucking away at this all. Read lots of posts, did lots of searching. Few more dead-ends.. BUT I concluded the same :+1:t2:

Had several attempts on creating an app.. until I found information about using “https://shopify.dev/apps/default-app-home, and uncheck “Embed app in Shopify admin”

- as per your suggestion

I did have a little trouble getting the scopes to work via the UI in the dev dashboard. I had to release and install a few revisions before I noted the correct permissions showing in the store.

I wonder if that is a small issue in the dev UI on that front.

I got to the process to generate the access token in a very round about way. Noted the expiry time on the access_token I am OK to refresh when the app needs to query.

I have this all in Paw (now renamed rapidAPI) now so I can experiment with the various queries I need for now (and refresh the token when needed).

All is working.

I am a little at a loss on how is appears (to my novice GraphQL skills) to be more difficult to get to the data I need compared to REST, but I am on the journey of learning all of that.

Next is exploring pagination and / or bulk operations.

Thank you for getting back to me. It’s great to know I landed on the correct solution and great to know I was correct to ask for clarification.

1 Like

Happy to help Dave, be sure to post another topic if you get stuck again - there are so many knowledgeable devs on the forum happy to help and myself and my colleagues are floating about too. Best of luck with your GraphQL journey!