Shopify Admin API GraphQL Overview

Shopify Admin API Endpoint - REST or GraphQL?

In this reference we’ll look at how to setup a connection to the Shopify Admin API using the GraphQL endpoint https://{store_name}.myshopify.com/admin/api/2022-04/graphql.json
There is also a REST API endpoint for the Admin API and you can utilize our purpose built connectors to easily:
But not everything is possible with the rest API because the GraphQL endpoint allows more expressive requests.
If possible use our purpose built connectors as they will manage pagenation, json parsing and a myriad of other difficulties. Otherwise the REST endpoint is next best and if you need complex queries use the GraphQL endpoint

Authorization

SyncWith will automatically handle authorization. Shopify requires us to send you to their app store and install our Shopify app which is different from how most vendors handle Oauth. Here is our guide to connect your Shopify account to SyncWith.

Don’t Have SyncWith?

  • SyncWith is a Google Sheets Add On
  • SyncWith allows you to move data from any API into Google Sheets
  • Learn more about why Marketers love SyncWith
  • Check out our Google Workspace Listing and see why we’re the highest rated API add on with over 100,000 installs. Install today and get your data into Sheets in minutes.
  • SyncWith is a Google Sheets Add On

Creating a Connection to the GraphQL Endpoint

  1. Click Extensions
  1. Choose SyncWith Addon
  1. Click Manage Connections
  1. Cut and paste the Admin API endpoint
    1. https://{store_name}.myshopify.com/admin/api/2022-04/graphql.json
  1. Set the method to POST
  1. Give the connection a name eg Shopify Orders
  1. Complete Authorization as per above if you have not already
  1. Select Shopify Login (Oauth) authentication
  1. Select your Shopify store (will only show if you’ve completed authorization)
  1. Syncwith automatically detects the path parameter {your-domain} in the endpoint path - so you just need to enter some non spaced random text. You don’t need to fill out your actual store name as we automatically populate it as long as you use Oauth and you’ve authenticated with Shopify. In this instance we wrote blahblahblah which will work.
notion image
 
notion image
We now have the basics of the connections setup but we must craft a query to send to the endpoint to describe which data we are requesting from Shopify. The Shopify Admin API provides data many objects, most useful are likely:
  • Inventory
  • Orders
  • Products and collections
  • Shopify Payments
  • Customers
  • Discounts and marketing
Shopify provides a complete outline of the GraphQL Admin API which can be a helpful resource, if we can’t cover what you want to in this document

Completing a Simple Query - First 10 Orders

Lets get a GraphQL created and working so we can refine our queries further once we know all the plumbing is working. In this example we’ll take a look at the first 10 orders. Shopify sorts the order results by createdAt in ascending order. So you’ll see your oldest orders first - as you go through the reference you’ll learn how to change the sort key and sorting.
{
	orders(first: 10) {
		edges {
			node { id }
		}
	}
}
  1. Enter your graphQL code into the Body setting of the
  1. Click Preview
  1. Preview window will open and show you something like
notion image
Try clicking the View raw data toggle
notion image
And you’ll see data like:
notion image
SyncWith lets you automatically handle your JSON expansions by clicking the + expand button
notion image
So to get the orders within the data object we simply click expand a couple times until we’re left with the underlying primitive we requested Orders.ID - as you expand the column names will show the path eg data.orders.edges.node.id
notion image

Shipping the Data into Google Sheets

To insert the order data into Google Sheets simply click Insert
notion image

Changing the Order of Results

Shopify GraphQL returns a list with a default sort key and a default sort order. For orders:
  • Default Sort Key: CREATE_AT
  • Default Sort Order: Ascending
If you want to change the order to Descending we can use the connection argument reverse which will reverse the default sort order. This argument is a boolean so reverse:true will return our products in most recent first
{
	orders(first:10 
This could be useful if you just wanted to sync your last 100 items ordered to a spreadsheet every 5 mins, so you always had a real time view of the orders coming through.
You can also change the sort order by specifying a sortKey, eg I want to sort the orders by largest total order price first for this you’d add the connection argument sortKey to orders and use TOTAL_PRICE. The valid sort keys for orders are:
CREATED_AT CUSTOMER_NAME FINANCIAL_STATUS FULFILLMENT_STATUS ID
ORDER_NUMBER PROCESSED_AT RELEVANCE TOTAL_PRICE UPDATED_AT
{
	orders(first:10 

Recap

  • We can now authenticate to Shopify
  • We can write a basic GraphQL query to get back our first 10 orders
  • We can specify a simple Order primitive like ID and get it back
  • We can handle the JSON transform and move the data into Google Sheets
  • We can’t filter the orders returned

Filtering a GraphQL Query

Our next item to tackle is figuring out how to filter the results Shopify GraphQL returns. But before we get to that it’s useful to have the Shopify GraphQL Explorer up in a window to help us with authoring our GraphQL.

Useful Tool: Shopify GraphQL Explorer

Shopify has an interactive GraphQL explorer and query engine that is great to test queries and see the results live. This is the best way to author your query and then cut and paste it into syncwith. It can be found at https://shopify.dev/graphiql/admin-graphiql
notion image

Adding a Query Connection Parameter to Filter Orders

If you want to do more advanced filtering on a request you need to use the query parameter which is one of the valid connection arguments for the Shopify objects. Lets take a look at quick example on how we might filter the order objects we want returned.
Filtering we want:
  • Orders created on or after Jan 1, 2022
  • Orders that have been cancelled
query:"created_at:>=2022-03-1 AND 
status:CANCELLED"
NOTE Filter parameters have different names than the GraphQL entities, eg if you’re requesting the primitive createdAt you might think you could filter in the query with the same property eg createdAt:>yyyy-mm-dd but you’d be wrong you need to use the filter param which is created_at so created_at:>yyyy-mm-dd.
NOTE Filter param values are case sensitive, despite the value returned from OrderDisplayFinancialStatus being all cap the value that must be provided in the filter param is lower case, eg the filter parameter financial_status takes paid not PAID, providing a value that is not cased correctly will not throw an error it will return values as if no filter param was provided which can be confusing.
Shopify does not have robust documentation on the valid params in the query statement other than to list which ones are available. It can be confusing which underlying object literal is used when filtering and what values to use. Continuing with our example we’ll look at the filter parameters for the Orders object. This guides is based on our best experience with using GraphQL in practice.

Available Filter Properties

Filter Parameters for GraphQL Orders Query
Name
Description / Example Values
cart_token
The shopping cart ID, use to correlate conversions with store visits where a user added an item to a cart.
channel_id
Allows you to filter based on the ChannelInformation.channelId field on the Order object. You’ll need to know the ID of the Channel in advance to use this filter as you can’t pass in the channel name, but you could report on orders and include the ID and name to learn what IDs to use. Similarly to source_name you can use the channel_id to search for default channels. Order.channelInformation.channelDefinition.channelName mappings are as follows web = Online Order pos = Point of Sale shopify_draft_order = Draft Orders iphone = Shopify Mobile for iPhone If you’re reporting using IDs you can get the channel Id from the channelInformation object - channelInformation { id }. The ID will come back in the form gid://shopify/ChannelInformation/3890967xxxx the ID is the int at the end: 3890967xxxx
chargeback_status
A shopify dispute / chargeback can be in one of the following states: accepted charge_refunded lost needs_response under_review won
checkout_token
Find the order (if any) associated with a previous checkout session.
created_at
DateTime format yyyy-mm-ddTHH:MM:SSZ For example query:"created_at:2021:12:25"
credit_card_last4
Last 4 digits of the credit card used
customer_id
Filters by Order.Customer.id. The Customer id can be retreived from the order object, it will have the shape: "customer": { "id": "gid://shopify/Customer/219851561xxxx"} . The ID you would pass in to see all orders from this customer would be 219851561xxxx
delivery_method
Each Order can have multiple Fullfillment Orders (eg the order required 3 shipments). Each FulfillmentOrder has a deliveryMethod. Each DeliveryMethod has an ID and a methodType. This filter takes DeliverMethods.DeliveryMethodType, valid values include: local none pick_up retail shipping For example query:"delivery_method:shipping"
discount_code
Filters based on discount code, for example query:”discount_code:NEW20” query:”discount_code:CYBERMONDAY OR discount_code:BLACKFRIDAY”
earliest_fulfill_by
Takes the earliest FulfillmentOrder.FulfillBy across all the FullfillmentOrders and lets you filter against that. Takes a DateTime (yyyy-mm-ddTHH:MM:SSZ) for example if you want all orders that will have some level of fulfillment on or before Dec 24 you would do query:"earliest_fulfill_by:<=2021-12-24"
email
Filters orders by order.customer.email for example: query:"email:supermario@nintendo.com"
financial_status
authorized displayed as authorized. expired displayed as expired. paid displayed as paid. partially_paid displayed as partially paid. partially_refunded displayed as partially refunded. pending displayed as pending. refunded displayed as refunded. voided displayed as voided. For example query:”financial_status:refunded OR financial_status:voided”
fulfillment_status
If filtering based on fulfilment_status is important it’s better to use the rest endpoint - Sync Shopify Orders - as there are known issues with this filter param and GraphQL: https://community.shopify.com/c/shopify-apis-and-sdks/fulfillmentorders-always-returning-empty-for-orders-in-graphql/td-p/934262
fulfillment_location_id
uses the order.fulfillments.location.id value to filter against for example query:"fulfillment_location_id:1587783xxxx"
gateway
Filters based on the order.paymentGatewayNames example values could include: paypal shopify_payments
location_id
Filters based on physicalLocation.id, often for online orders this will often be null, when source_name:pos is true you have a better chance of getting a location.
name
Filters based on Order.name - which is the unique identifier for the order that appears on the order page in the Shopify admin and the order status page.
payment_provider_id
This filter param appears to not work with GraphQL and doesn’t respond to ids such ase order.transactions.paymentProvider.id even with read_payment_settings permission. If you have any information on this let us know at support and we’ll update the guide.
processed_at
Filters based on Order.processedAt Takes a DateTime (yyyy-mm-ddTHH:MM:SSZ) for example if you want all orders that were processed on the 21st of December you’d use the query query:"processed_at:>=2021-12-21 AND processed_at:<2021-12-22"
reference_location_id
return_status
This lets you filter orders where there is a return in_progress or completed return (returned).
risk_level
Filters based on the order.riskLevel so if you wanted to see orders that are medium risk you’d use query:"risk_level:medium")
sales_channel
All sales channels are apps, but only some apps are sales channels. sales_channel seems to accept the numeric ID of the app, eg order.channelInformation{app{id}}
source_name
This will allow you to filter by the source_name handle of the registered channels including the default, examples include web pos instagram shopify_draft_order iphone android
status
There is no specific status field on the orders object, so this is useful for finding orders of a given type, available values include: all open closed cancelled
tag
Filters based on order.tags if you wanted to look at all orders with non null values in the tag field you could try query:"tag:*". If you knew that all pre orders were tagged pre-order you could try query:"pre-order"
tag_not
This will return all orders except where order.tags contains the value specified. For example if you want all orders except pre orders you might use query:"tag_not:pre-order"
test
updated_at
Filters based on order.updatedAt takes a DateTime (yyyy-mm-ddTHH:MM:SSZ) for example if you want all orders that were last updated after the 21st of December you’d use the query query:"processed_at:>=2021-12-21

Operators

If you want to join filters together you can use connectives
  • AND OR NOT
  • state:enabled OR state:disabled
  • eg NOT source_name:iphone AND NOT source_name:android
status:cancelled OR status:closed

Comparators

: equality :< less than :> greater than :<= less than or equal to :>= greater than or equal to
A common use for comparators are to narrow a graphQL query to a specific date range, for example all orders that came in the previous month
{
	orders(
		first:200, 
		reverse:true,
		query:"created_at:>=2021-11-01 AND created_at:<=2021-11-30"
	)
	{
		edges{
			node {
				id
			}
		}
	}
}

Grouping Filters

Use parentheses to group queries and logic for example mobile app transactions that have closed
(source_name:iphone OR source_name:android) AND status:closed 

Recap

  • We can now filter our query to ensure we’re only getting the orders back of interest
  • We can set our sort key and sort ordering
  • We’ve seen a couple examples of how to specify the values but need more instruction on how get the the data we want out of graphQL
  • We are still only returning a fixed number of results eg first:200, we need more instruction on how to page through results

Understanding Pagination with the Shopify GraphQL API

GraphQL has the concept of a cursor which allows you to understand the position of any result in a list. The reason we need to cursor through results is Shopify limits the maximum amount of data each request to the GraphQL endpoint will return. Also it requires you to specify the number of items you want back eg first:20. Since you can’t know in advance, say how many orders were placed in the last 7 days, you need to be able to page through all the results of the query not just the first X.
To frame pagination lets say we want to look at the last 90 days of orders, and lets say there are 1500 orders that meet that criteria - it is impossible with graphQL to get those all back in a single request, pagination lets us:
  1. Send the query to Shopify
  1. Get x results
  1. Get the ID of the last result
  1. Send another query and ask for the x more items starting after the last result (using the connection argument after)
  1. And so on and so forth till there are no more results.
Lets start with a simple query with no pagination:
{
	orders(first:3){
		edges{
			node { id }
		}
	}
}
We get results:
"data": {
    "orders": {
      "edges": [
        {
          "node": {
            "id": "gid://shopify/Order/1379031122002"
          }
        },
        {
          "node": {
            "id": "gid://shopify/Order/1379031253074"
          }
        },
        {
          "node": {
            "id": "gid://shopify/Order/1379031318610"
          }
        }
      ]
    }
  }
We need to be able to get the cursor from the last item, so lets change the query slightly to tell Shopify to output the cursor id of each edge / order
{
	orders(first:3){
		edges{
			node { id }
			cursor #getting the cursor to each specific edge / order
		}
	}
}
Now we can see the cursor for each item in the response
{
  "data": {
    "orders": {
      "edges": [
        {
          "node": {
            "id": "gid://shopify/Order/1379031122002"
          },
          "cursor": "eyJsYXN0X2lkIjoxMzc5MDMxMTIyMDAyLCJsYXN0X3ZhbHVlIjoiMjAxNi0wNi0xNCAwMzo0MDo0Mi4wMDAwMDAifQ=="
        },
        {
          "node": {
            "id": "gid://shopify/Order/1379031253074"
          },
          "cursor": "eyJsYXN0X2lkIjoxMzc5MDMxMjUzMDc0LCJsYXN0X3ZhbHVlIjoiMjAxNi0wNi0xOCAwMzowNjo1NS4wMDAwMDAifQ=="
        },
        {
          "node": {
            "id": "gid://shopify/Order/1379031318610"
          },
          "cursor": "eyJsYXN0X2lkIjoxMzc5MDMxMzE4NjEwLCJsYXN0X3ZhbHVlIjoiMjAxNi0wNi0yMyAyMTo1MTowNS4wMDAwMDAifQ=="
        }
      ]
    }
  }
Now say we picked one of these cursors, eg the second one eyJsYXN0X2lkIjoxMzc5MDMxMjUzMDc0LCJsYXN0X3ZhbHVlIjoiMjAxNi0wNi0xOCAwMzowNjo1NS4wMDAwMDAifQ== which corresponds to order ID 1379031253074. If we passed that into the graphQL with after: then it should do 3 orders starting after that order, so it should show us order 1379031318610 again and then two more.
Query specifying we want to first 3 results after order 1379031318610
{
	orders(
	first:3, 
	after:"eyJsYXN0X2lkIjoxMzc5MDMxMjUzMDc0LCJsYXN0X3ZhbHVlIjoiMjAxNi0wNi0xOCAwMzowNjo1NS4wMDAwMDAifQ=="
	){
		edges{
			node { id }
			cursor #getting the cursor to each specific edge / order
		}
	}
}
As expected the next 3 starting with order 1379031318610
{
  "data": {
    "orders": {
      "edges": [
        {
          "node": {
            "id": "gid://shopify/Order/1379031318610"
          },
          "cursor": "eyJsYXN0X2lkIjoxMzc5MDMxMzE4NjEwLCJsYXN0X3ZhbHVlIjoiMjAxNi0wNi0yMyAyMTo1MTowNS4wMDAwMDAifQ=="
        },
        {
          "node": {
            "id": "gid://shopify/Order/1379031384146"
          },
          "cursor": "eyJsYXN0X2lkIjoxMzc5MDMxMzg0MTQ2LCJsYXN0X3ZhbHVlIjoiMjAxNi0wNy0xMyAxNzo1OTowMi4wMDAwMDAifQ=="
        },
        {
          "node": {
            "id": "gid://shopify/Order/1379031482450"
          },
          "cursor": "eyJsYXN0X2lkIjoxMzc5MDMxNDgyNDUwLCJsYXN0X3ZhbHVlIjoiMjAxNi0wNy0xNiAyMDo0NDozNy4wMDAwMDAifQ=="
        }
      ]
    }
  }
You can try this out for yourself and this will help you understand how a cursor and the connection argument after will allow you to page through many results with multiple requests.
 
We don’t want to enumerate all our orders since the beginning of time so lets make it time bound, say the first 7 days of orders in December.
{
	orders(
	first:5, 
	query:"created_at:>=2021-12-01 AND created_at:<=2021-12-07"
	){
		edges{
			node { id }
			cursor #getting shopify to output the cursor here
		}
	}
}
We’re now getting the first 5 results back and now we need code to enumerate and send more requests to keep getting the next 5 until we have all the orders returned.
SyncWith will automatically handle pagination ( you can see our pagination tutorial for all the types of pagination we support ) to handle pagination with a cursor, we just need to tell SyncWith two things:
  • The path that contains the cursor
  • The variable/parameter that contains the cursor value
Once you have the path and parameter you add them to the SyncWith pagination setup, for example
notion image
notion image
  • Above we tested a fixed cursor after:"eyJsYXN0X2lkIjoxMzc5MDMxMjUzMDc0LCJsYXN0X3ZhbHVlIjoiMjAxNi0wNi0xOCAwMzowNjo1NS4wMDAwMDAifQ==" to show how the after parameter works.
  • We need a dynamic cursor that gets updated on each request
  • Synwith lets us pass in a dynamic value to the endpoint. To do this we need to add a variable in the GraphQL statement such that the connection argument after:
  • SyncWith will handling updating this variable with the appropriate cursor
  • This is why we need to tell SyncWith
    • the variable name
    • the path to the cursor
query ($after: String) #adding the variable after which is of type string
{
	orders(
	first:5, 
	query:"created_at:>=2021-12-01 AND created_at:<=2021-12-07",
	after:$after #telling shopify to start at after our variable $after
	){	
		edges{
			node { id }
			cursor #getting shopify to output the cursor here
		}
	}
}
Breaking this down:
  • We have a variable called a$fter that is used to tell Shopify where to start
  • We have the cursor being outputed at every edge.
  • We’re telling Syncwith what the variable name is variables.after
  • We’re telling Syncwith the path to the cursor data.orders.edges.4.cursor
  • SyncWith will dynamically replace the variable $after with the correct cursor value
  • We’re telling Syncwith that the value of the cursor is on the 4th edge - eg the last order returned by the previous query
  • SyncWith will conduct multiple queries incrementing the cursor on each query
  • The reason we use edges.4.cursor and not edges.5.cursor is because the edges are returned as an array and the first item is 0, so the 5th item is array item 4
  • If you change the number of orders to sat first:20 then you’ll need to update the currsor path to be data.orders.edges.19.cursor, to make sure you’re always getting the cursor from the last item returned.
SyncWith will now keep sending requests to the Shopify API until no more results are left to cursor through. Alternatively you can specify a page limit, so that for instance you do at most 100 requests (each returning 5 orders).

Recap

  • We can now write a query and filter to get only the items we want
  • We can paginate the results, format the JSON and get the data into Google Sheets
  • Can’t access various items within an object, for example if we’re getting orders back:
    • How do we get values from objects within the order
    • How do we enumerate collections within an object - eg the line items that make up the order or the transactions that make up the full payment, etc.

Primitives, Objects and Collections

You’ll need to familiarize yourself with the specific object you’re requesting back to understand it’s structure. In this instance we’ll look at the order object whose specs can be found here.

Objects

Some fields some are objects like:
  • Order.billingAddress - type MailingAddress
  • Order.currentTotalPriceSet - type MoneyBag
With objects you need to open them up to find the primitives (primitives are things like: Integer, Enumeration, String, Float, ID or DateTime. For example Order.billingAddress.address1 which is a string. You have to be specific in GraphQL on what you want you cannot just select an object without ultimately specifying a primitive.
For example you might think that if you had a query like
{
	orders(first:10){
		edges{
			node { id billingAddress }
		}
	}
}
Shopify would simply return the billingAddress object as nested JSON but it will not. It will throw the error Field must have selections (field 'billingAddress' returns MailingAddress but has no selections. Did you mean 'billingAddress { ... }'?):
"errors": [
    {
      "message": "Field must have selections (field 'billingAddress' returns MailingAddress but has no selections. Did you mean 'billingAddress { ... }'?)",
      "locations": [
        {
          "line": 4,
          "column": 14
        }
      ],
      "path": [
        "query",
        "orders",
        "edges",
        "node",
        "billingAddress"
      ]
You have to specify a selection of at least one primitive (here we specify 5):
{
	orders(first:10){
		edges{
			node { id billingAddress

Primitives

Primitives are simple they are not objects or collections they are a value ( Integer, Enumeration, String, Float, ID or DateTime for example). Sticking with our example of a Shopify Order here are some example primitives:
Example Order fields that are primitives include:
  • Order.cancelledAt - DateTime
  • Order.confirmed - Boolean
  • Order.discountCode - String
  • Order.email - String
  • Order.id - ID
  • Order.note - String
  • Order.refundable - Boolean
If you wanted to select these primitives you simply include them inside the curly braces for the object you’re requesting, eg:
{
	orders(first:10){
		edges{
			node { 
			cancelledAt
			confirmed
			discountCode
			email
			id
			note
			refundable
			}
		}
	}
}

Collections

Collections are a group of one or more objects, for example orders can have many
  • Line Items - eg an order is made up of 5 products
  • Events - an order has many events from it’s creation to it’s delivery, each event is tracked seperately
  • Agreements - an order can have many sales agreements
  • Fulfillments - an order can require multiple shipments to fulfil the order
 
With graphQL when you have a collection of objects we need to specify the object and then edges { node {} } to tell Shopify that we want to get all of the objects in the collection.
We’ve already seen this with the Orders collection:
{
	orders(first:10){ # orders is a collection
		edges{
			node {id} # the node is a specific order of which we want the ID
		}
	}
}
We’ve also learned that collections can take arguments for example:
orders(first:10) #the order collection can take many arguments in this case first
Shopify Documentation will show you the arguments available. For example Order.fulfillments has one available argument first.
notion image
Whereas Order.LineItems takes many more arguments:
  • first
  • after
  • last
  • before
  • reverse
notion image

Getting a Collection of Collections - Eg Orders & LineItems

Lets start with the basic syntax and then expand the query from there:
{
	orders(first:2){ # orders is a collection
		edges{
			node {
				id #order id
				
In the above example we’re getting the first 2 orders and getting the first 10 line items for each of those orders. If we look at the response we can see that:
  • We got two orders back as expected
  • We got 3 line items on the first orders and 4 line items on the second order
{
  "data": {
    "orders": {
      "edges": [
        {
          "node": {
            "id": "gid://shopify/Order/1379031122002",
            "lineItems": {
              "edges": [
                {
                  "node": {
                    "id": "gid://shopify/LineItem/3010689663058"
                  }
                },
                {
                  "node": {
                    "id": "gid://shopify/LineItem/3010689695826"
                  }
                },
                {
                  "node": {
                    "id": "gid://shopify/LineItem/3010689728594"
                  }
                }
              ]
            }
          }
        },
        {
          "node": {
            "id": "gid://shopify/Order/1379031253074",
            "lineItems": {
              "edges": [
                {
                  "node": {
                    "id": "gid://shopify/LineItem/3010690121810"
                  }
                },
                {
                  "node": {
                    "id": "gid://shopify/LineItem/3010690154578"
                  }
                },
                {
                  "node": {
                    "id": "gid://shopify/LineItem/3010690187346"
                  }
                },
                {
                  "node": {
                    "id": "gid://shopify/LineItem/3010690220114"
                  }
                }
              ]
            }
          }
        }
      ]
    }
  }
Now you might see the conundrum that is common with GraphQL - how to know how many line items there might be? We asked for 10, but what if your some orders had 15 line items? Turns out you can set a cursor on the line items as well to paginate the line items inside a given order. SyncWith only supports a single outer cursor at this point, so it’s best to pick a value that satisfies most orders, eg if you do first:50 then as long as your orders do not have more than 50 line items they will all be returned.

Getting Values out of a Collection

In the above example we only got the id of the line item, we could modify the query to get more than just the id:
{
	orders(first:1){ # orders is a collection
		edges{
			node {
				id #order id
				lineItems(first:2){edges{node{ #first 10 lineitems for each order
					
and the results:
{
  "data": {
    "orders": {
      "edges": [
        {
          "node": {
            "id": "gid://shopify/Order/1379031122002",
            "lineItems": {
              "edges": [
                {
                  "node": {
                    

Adding Pagination - Last 7 days of Orders with Line Items

query ($after: String) #after variable used to paginate our orders
{
	orders(first:5 after:$after query:"created_at:>2022-05-01"){ # orders is a collection
		edges{
			node {
				id #order id
				lineItems(first:2){edges{node{ #first 2 lineitems for each order
					id #lineitem id
					sku #lineitem sku
					quantity #number ordered of the line item
					name #name of the product
					discountedTotalSet { presentmentMoney { amount } } #price of the item
				}}}
			}
			cursor
		}
	}
} 
  • Set your variable to variables.after
  • Set your path to data.orders.edges.4.cursor
  • Hit preview
The SyncWith preview window will do at most 3 requests, we can see the pagenation is working because there are 3 responses:
notion image
We can then click expand a few times and get our JSON converted to a table format:
notion image
And if we want we can use SyncWith to move this data to our platform of choice, in this case Google Sheets:
notion image

Writing our First Query that Requires Pagination

The above query shows pagination but it’s only requesting the 10 orders, lets create a query that requires

Managing GraphQL Quota

In the above example we were
  • requesting 5 orders at a time
  • first 2 line items per order
In the response shopify shows us some information about the query cost and budget used:
"extensions": {
    "cost": {
     
Shopify calculates a theoretical cost per request called requestedQueryCost which is roughly the number of rows * columns if you flattened the data. Eg 10 orders * 10 line items * 10 values = 1000 rows * 10 columns = requested query cost of 1000. This is just a rule of thumb to use when trying to get your costs in line.
When the query runs some orders will have less than 10 line items, say an average of 3.5, so the request might only have a query cost of 10 orders * 3.5 line items * 10 values = 350.

Shopify GraphQL Rate Limits

  • Your requested query cost cannot exceed a cost of 1000
  • You have a budget that you burn through, you start with 1000 and you restore at 50 / second
  • Your actual query cost comes out of your budget
  • Your currentlyAvailable determines whether Shopify will accept your query
    • If requestedQueryCost > currentlyAvailable it will not run
    • If requestedQueryCostcurrentlyAvailable it will run

Pagenation

  • If you’re paginating you will be running multiple queries
  • Say each request has a real cost of 200
  • Say each request takes 2 seconds to complete
  • During each request running you will burn 200 and restore 100, so you’ll lose 1000 from your initial allocation of 1000
  • If your query requires you to paginate through more than 10 pages your 11th request will fail
  • But the sticker is if you have a high requestedQueryCost that has a low actualQueryCost you can run into the issue where you can’t run queries even though actualQueryCostscurrentlyAvailable

Balancing # of Requests / Query Cost / Query Budget and Query

We’ll use the line item and order example as it’s instructive of the balances we need to make
We need to set the first: property high enough that we’re getting all of our line items. We don’t need to worry about the actual cost of doing so just the theoretical cost. Lets say 50 is enough to cover even the largest of orders
Lets update the query:
query ($after: String) #after variable used to paginate our orders
{
	orders(first:5 after:$after query:"created_at:>2022-05-01"){ # orders is a collection
		edges{
			node {
				id #order id
				lineItems(first:50){edges{node{ #first 2 lineitems for each order
					id #lineitem id
					sku #lineitem sku
					quantity #number ordered of the line item
					name #name of the product
					discountedTotalSet { presentmentMoney { amount } } #price of the item
				}}}
			}
			cursor
		}
	}
} 
this yields a requested cost of 517 and an actual cost of 59
"extensions": {
    "cost": {
      "requestedQueryCost": 517,
      "actualQueryCost": 59,
      "throttleStatus": {
        "maximumAvailable": 1000,
        "currentlyAvailable": 941,
        "restoreRate": 50
      }
    }
Lets bump up the number orders we’re getting per request to 20 and set the line items to first 35
query ($after: String) #after variable used to paginate our orders
{
	orders(first:20 after:$after query:"created_at:>2022-05-01"){ # orders is a collection
		edges{
			node {
				id #order id
				lineItems(first:35){edges{node{ #first 2 lineitems for each order
					id #lineitem id
					sku #lineitem sku
					quantity #number ordered of the line item
					name #name of the product
					discountedTotalSet { presentmentMoney { amount } } #price of the item
				}}}
			}
			cursor
		}
	}
} 
and we get an error because we’re exceeding the max requested query cost:
{
  "errors": [
    {
      "message": "Query cost is 1462, which exceeds the single query max cost limit (1000).\n\nSee https://shopify.dev/concepts/about-apis/rate-limits for more information on how the\ncost of a query is calculated.\n\nTo query larger amounts of data with fewer limits, bulk operations should be used instead.\nSee https://shopify.dev/tutorials/perform-bulk-operations-with-admin-api for usage details.\n",
      "extensions": {
        "code": "MAX_COST_EXCEEDED",
        "cost": 1462,
        "maxCost": 1000,
        "documentation": "https://shopify.dev/api/usage/rate-limits"
      }
    }
  ]
}
so if we reduce the number of orders to first:13 we should get under the limit and we do our costs now look like:
"extensions": {
    "cost": {
      "requestedQueryCost": 951,
      "actualQueryCost": 137,
      "throttleStatus": {
        "maximumAvailable": 1000,
        "currentlyAvailable": 863,
        "restoreRate": 50
      }
    }
  }
The problem now is if we’re paginating, the second query will go out, it will have a theoretical cost of 951 an we’ll only have 863 remaining, so we’ll get an error on the 2nd paginated request:
{
  "errors": [
    {
      "message": "Throttled",
      "extensions": {
        "code": "THROTTLED",
        "documentation": "https://shopify.dev/api/usage/rate-limits"
      }
    }
  ],
  "extensions": {
    "cost": {
      "requestedQueryCost": 951,
      "actualQueryCost": null,
      "throttleStatus": {
        "maximumAvailable": 1000,
        "currentlyAvailable": 897,
        "restoreRate": 50
      }
    }
  }
}
You can see that the currentlyAvailable of 897 on the second request is slightly more than the currentlyAvailable after the first request - this is because between the requests Shopify restored some of our query credit.
The right approach is to reduce the theoretical query cost down such that it never falls below maximumAvailable. Since we don’t want to miss line items we simply lower the number of orders returned per request, say 8 for example, we can look at 3 of the successive requests in the preview window to see how quickly our credits deplete:
notion image
We can see between what we restore and we use we’re burning through net ~50 credits per request, so after about 9 queries (9 x 8 = 72 orders) our maximum available is going to be less than our requestedQueryCost. We could try to further reduce the orders returned until we reach some sort of balance such that the query could run indefinitely.

Need Help?

  • Are you using the Shopify GraphQL to move shopify data somewhere
  • We’d love to hear your use cases
  • Need to move and keep Shopify data sync’d to a database, BigQuery, PowerBI, S3, etc. We’d love to hear from you to see if we can help