Please flag GraphQL Migration blockers here

New blocker came up for us today.

requiresShippingMethod appears to have been added to the FulfillmentService object in GQL API 2025-04, but at some point in the last few days it was set to true for everyone, even Fulfillment Services on older API versions who are already blocked on migrating to newer versions (we’re currently on 2024-10). That caused a TON of breaking changes for many of our client shops who, despite our best efforts, assign “digital” products to our location from time to time due to requirements from other 3rd party apps and the like.

However, it appears requires_shipping_method is available in 2024-10 of the REST API, which begs the question: why in the world would you suddenly apply that change to everyone if you aren’t going to port that field back to older, supported GQL API versions?

The “abandonedCheckout” object in the GraphQL, does not have the property “shippingLines”, which is crucial for app to access the shipping method selected by the customer before they abandoned the checkout. (release candidate and unstable version do not have this too)

The REST API have the “shipping_lines” property, it just says deprecated without any alternative in graphQL.

Shop.primary_locale isn’t exposed on Admin GraphQL. The only path is shopLocales, which forces the read_locales scope and breaks older installs that didn’t request it. This adds friction for a single read-only value during a GraphQL migration.

Reading settings_data.json via GraphQL returns non-JSON because of the warning header. Relying on parsing a commented file is brittle; we need a typed way to query whether specific app-embed blocks exist and whether they’re enabled on the current main theme.

FulfillmentService.requiresShippingMethod changed behavior recently and is effectively applied across shops, causing breakage for services handling digital SKUs while many apps are pinned to older GraphQL versions. Field behavior should remain stable on supported versions or be backported for parity (FulfillmentService)

New blocker came up today. Similar to the blocker mentioned above by Ruben_Stacksync:

order.customer is exposed in the Admin REST API, but is blocked in the GraphQL API requiring the additional read_customers scope. In our case, all we need is the customer.id (no other customer data is needed by us) so that we can process GDPR webhooks effectively. The addition of this new scope requirement, and the fact it is out-of-sync between APIs, effectively breaks existing installs that didn’t request that scope. Having to send our team out to every client to explain this requirement and why it means we need full read_customers access to all their stores isn’t something I’ll be able to get internal buy-in for.

Hello everyone,

I am trying to migrate a products query that I run for stores from REST to graphQL the code seems to be working - however for one particular client I am 705 items missing :frowning: I think it is to do with archived products, which I am querying so I cannot see why when I do this as REST it works correctly and graphql as not, for reference on the REST i use a status=any [which is not acceptable on grapql]. The problem I also have with this client is they are using the taxonomy for product descriptions and this value doesn’t seem to exist in the REST version.

{
	"query": "query { products( first: 250 sortKey: CREATED_AT query: \"created_at:>=2000-01-01 AND created_at:<=2026-01-01 AND status:'active' OR status:'archived' OR status:'draft'\") 
	{ edges 
	  { 
	    cursor 
	    node 
		{ id 
		  legacyResourceId 
		  title 
		  productType
		  status
		  category { id } 
		  createdAt 
	      updatedAt 
	      variants(first: 250) { 
			edges 
		    { node 
				{ id
				  displayName
				  legacyResourceId 
				  createdAt 
				  updatedAt 
				  sku 
				  title 
				  price 
			      compareAtPrice 
				  inventoryItem 
				   {  id 
				      legacyResourceId 
					  unitCost {
						amount 
						currencyCode }
				}
			}
		}
		
	  }
		}
		}  
		pageInfo 
		{ hasNextPage endCursor }
		}
		}"
}

This is my current query, that works apart from missing 700+ products.

I have tried various things but it makes no difference.

When I requery it i change it to [where the zzzzzz is replaced with the next cursor]

"query": "query { products( first: 250 sortKey: CREATED_AT after: \"ZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZ\" query: \"created_at:>=2000-01-01 AND created_at:<=2026-01-01 AND status:'active' OR status:'archived' OR status:'draft'\") 
	{ edges 
	  { 
	    cursor 
	    node 
		{ id 
		  legacyResourceId 
		  title 
		  productType
		  status
		  category { id } 
		  createdAt 
	      updatedAt 
	      variants(first: 250) { 
			edges 
		    { node 
				{ id
				  displayName
				  legacyResourceId 
				  createdAt 
				  updatedAt 
				  sku 
				  title 
				  price 
			      compareAtPrice 
				  inventoryItem 
				   {  id 
				      legacyResourceId 
					  unitCost {
						amount 
						currencyCode }
				}
			}
		}
		
	  }
		}
		}  
		pageInfo 
		{ hasNextPage endCursor }
		}
		}"
}

Can anyone point me in the right direction please?

Hey @Lucy_hutchinson - a couple of things jumping out at me here that should get you sorted.

1. The missing products could be due to your date range filter

Your query filters to created_at:<=2026-01-01, but we’re now in March 2026. Any products created after January 1st would be excluded. For a store with 1717 products, that could easily account for the 705 you’re missing.

If your goal is simply “all products regardless of when they were created”, you can drop the date range entirely and just filter by status.

2. Status filter syntax

The GraphQL equivalent of REST’s status=any is a comma-separated list without quotes:

status:active,archived,draft

In your original query you’re using status:'active' OR status:'archived' OR status:'draft', while the OR precedence should technically group correctly, the comma-separated syntax is the documented approach for this filter and avoids any ambiguity.

When you tried the comma-separated version and got the "Input active,archived,draft is not an accepted value" error, it looks like the values were wrapped in quotes (status:'active,archived,draft'). Single quotes in the search syntax create a phrase match, so Shopify sees that as one literal string. Drop the quotes and it should work.

3. Simplified query

Putting it together, here’s a cleaned-up version:

query ($cursor: String) {
  products(first: 250, after: $cursor, query: "status:active,archived,draft") {
    edges {
      cursor
      node {
        id
        legacyResourceId
        title
        productType
        status
        category {
          id
          name
        }
        createdAt
        updatedAt
        variants(first: 250) {
          edges {
            node {
              id
              displayName
              legacyResourceId
              createdAt
              updatedAt
              sku
              title
              price
              compareAtPrice
              inventoryItem {
                id
                legacyResourceId
                unitCost {
                  amount
                  currencyCode
                }
              }
            }
          }
        }
      }
    }
    pageInfo {
      hasNextPage
      endCursor
    }
  }
}

Pass $cursor as null for the first request, then use the endCursor value from each response for subsequent pages — which it looks like you’re already doing correctly.

4. On the taxonomy/category field

Good news here: the category { id name } field you’re using in GraphQL maps to the Shopify Standard Product Taxonomy. This is actually one of the advantages of moving to GraphQL. The REST API only exposes the free-text product_type field, so the structured taxonomy data is a GraphQL-only feature. You’re already pulling it correctly in your query.

Let me know if you’re still seeing a mismatch in product counts after removing the date filter — if so, share the x-request-id header from the response and I can dig in further on our end.

I’ve tried with longer dates, the missing products are archived products - and are still missing.

Even if I take the dates back to 1970. The actual creation dates according to the REST API are 2018-2026.

I’ve tried without the quotes and it doesn’t work on curl.

I have bodged it for now - but it’s took me a couple of hours. I have done a REST query to get all the products, and then done a manual download and overlaid the missing information and a manual update to fix the table.

I have tried all of what has been suggested and none of it works. Hence the questions.

I am aware that REST is being retired but at the moment the QL is ropey and unreliable.

Thanks

Lucy

For context I have just run this without any date parameters and I get 1005 products, when I am expecting 1717 Products. I have put the two base queries below. I issue these through a delphi APP which fires them over via the REST API [which makes curl equivalent requests]

I first issue

{"query": "query { products( first: 250 sortKey: CREATED_AT query: \"status:'active' OR status:'archived' OR status:'draft'\") { edges { cursor node { id legacyResourceId title productType status category { id } createdAt updatedAt variants(first: 250) { edges { node { id displayName legacyResourceId createdAt updatedAt sku title price compareAtPrice inventoryItem {  id legacyResourceId unitCost { amount currencyCode }}}}}}}  pageInfo { hasNextPage endCursor }}}"}

And then issue with the update cursor

{"query": "query { products( first: 250 sortKey: CREATED_AT after: \"ZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZ\" query: \"status:'active' OR status:'archived' OR status:'draft'\") { edges { cursor node { id legacyResourceId title productType status category { id } createdAt updatedAt variants(first: 250) { edges { node { id displayName legacyResourceId createdAt updatedAt sku title price compareAtPrice inventoryItem {  id legacyResourceId unitCost { amount currencyCode }}}}}}}  pageInfo { hasNextPage endCursor }}}"}

I follow this same logic, for abandonded checkouts, customers and orders and all work as expected. It’s only products that don’t seem to work correctly at the moment.

@Liam-Shopify Do you have any other ideas?

@KyleG-Shopify You helped me before with an orders issue so I am tagging you as well.. Sorry!

Thanks

Lucy

Hey Lucy, since the only missing products are the archived products, can you test with only that variable status:archived to see if any are returned when you aren’t also querying the others? Are you able to query the missing products directly (by ID). This is to rule out any permissions differences.

Some other things to check to narrow down if the issue is how the request is being parsed vs the products not being available in the graphql query:

  • If you add reverse:true does that change anything?
  • Do you get the same without the sortKey?
  • On your last page of results, is hasNextPage false?
  • What does productsCount return?

Also, when testing, add the debugging headers to see exactly how it’s being parsed.

OK.

Well today I split out the products and tested each type as suggested.

I got the right results.

So i then dug further into each one.

I have tested both ways and everything now seems to work as expected.

I did find a memory leak today in my code, which had not flagged until today so it appears currently the error is with my coding.

Sorry for the grief, I will go away and hang my head in shame.

Regards

Lucy

Glad you figured this out @Lucy_hutchinson - and thanks for the update! Very easy thing to miss :slight_smile: Let us know if you run into any other issues!

We are observing a troubling change in how custom apps appear to be provisioned. I originally posted in this thread, but figured it’d be good to have a record of it in this clearinghouse.

Throughout the last few months, our team has been creating a ton of apps to test auth schemes in the new apps architecture… around the end of January, those apps stopped registering a sales channel. This only came to our attention because we started seeing errors in Product queries with the publishedAt and publishedOnCurrentPublication fields in their selections. Without a “current” publication (presumably, the app’s default/included sales channel), we get Your app doesn't have a publication for this shop.

I believe this is what @David_Vrsek observed in their thread, and possibly what is causing this error reported by @Ranjan_Nagarkoti.

Given the historic availability of published_at and published_scope, I would consider this a breaking change from the REST API—or perhaps even internally, within a single GraphQL API version? We are effectively without an official way to allow merchants to selectively synchronize products with our tool (a plugin for a self-hosted content management system).

Questions

  1. Do we now need to instruct each of the developers implementing this plugin to create a public app and submit it for review just to get a sales channel? Will these apps be stuck in the same review queue that is experiencing major growing pains?
  2. When and why was this change implemented, and how can we be sure that our integration instructions are stable, going forward? Nothing in the changelog seems relevant to this.
  3. How should we recommend our users prevent exposing products from other sales channels to the synchronization tool? (We have some ideas here and will propose them in our upgrade guide… but so far, most of the options involve meta fields and/or examining GraphQL API results and discarding records based on user-defined conditions in our software.)
  4. (Bonus!) How does the Headless app create new sales channels, on-the-fly?

As an addendum to #1—if we do indeed need to ask that every one of our users submit an app for review, does that further clog up the pipeline? :grimacing:

I am happy to acknowledge that our use case is unusual (a plugin used in a self-hosted or “on-prem” system) may not agree with the new “app” system’s design, which we gather is intended to handle many end-users, rather than be provisioned 1:1 with stores.

We have been making a ton of adjustments to our integration and documentation so that upgrading is sensible and tenable for users… but it’s a shame that basic tasks like accessing product data have become so cumbersome (compared to just grabbing credentials from the store admin and firing off a paginated query to the REST API).

Taking a step back… please let me know if we can provide any more detail about the shift in app capability!