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 that is easier to use if you don’t need the full expressiveness of GraphQL and we’d recommend you try to use that first.
In the article we’ll cover:
- Lots of graphQL query examples
- Creating your first query
- Authorization
- Filtering GraphQL
- Operators / Comparators
- Pagination
- Primitives, Objects and Collections
- Managing your GraphQL quota
We don’t cover any specific programing language, the focus is on GraphQL, responses and the intricacies of the endpoint.
Table of Contents
Shopify Admin API Endpoint - REST or GraphQL?Table of ContentsJust Want your Shopify Data?No More Pitches, Help Me Use the API DirectlyAuthorizationCreating a Connection to the GraphQL EndpointCompleting a Simple Query - First 10 OrdersShipping the Data into Google SheetsChanging the Order of ResultsRecapFiltering a GraphQL QueryUseful Tool: Shopify GraphQL ExplorerAdding a Query Connection Parameter to Filter OrdersAvailable Filter PropertiesOperatorsComparatorsGrouping FiltersRecapUnderstanding Pagination with the Shopify GraphQL APIRecapPrimitives, Objects and CollectionsObjectsPrimitivesCollectionsGetting a Collection of Collections - Eg Orders & LineItemsGetting Values out of a CollectionAdding Pagination - Last 7 days of Orders with Line ItemsWriting our First Query that Requires PaginationManaging GraphQL QuotaShopify GraphQL Rate LimitsPagenationBalancing # of Requests / Query Cost / Query Budget and Query Need Help?
Just Want your Shopify Data?
If you don’t want to call the API directly and write glue code and just want the data sync’d to your data platform of choice SyncWith does this for 100,000s of users and businesses.
- Syncing Shopify
to
Google Sheets
- install our Google Sheets addon used by 100,000s of other data users, read more about the addon, some Shopify specific examples: - Sync Shopify Orders to Google Sheets
- Sync Shopify Products to Sheets
- Sync Shopify API
to
Airtable
- install our airtable addon in the marketplace
- Connect Shopify API Data
to
Excel
- install our Excel addon or contact support
- Export Shopify API Data
to
Data Studio
- contact support to join our beta
- Export Shopify API Data
to
PowerBI
- contact support to join our beta
- Shopify API
to
Databases
(S3, BigQuery, MySQL, Postgres) - contact support to join our beta
.jpg)
No More Pitches, Help Me Use the API Directly
Onwards with the API guide. We’ll focus on the Admin API endpoint and orders / products specifically and focus on how the GraphQL endpoint works. Many of the screenshots use the syncwith product to easily call the api and pass graphQL queries in.
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.
Creating a Connection to the GraphQL Endpoint
For the purpose of illustrating GraphQL, we’ll be calling the endpoints using SyncWith, but the queries and responses will be just as relevant if you’re calling the endpoint via your language of choice.
- Click Extensions
- Choose SyncWith Addon
- Click Manage Connections
- Cut and paste the Admin API endpoint
https://{store_name}.myshopify.com/admin/api/2022-04/graphql.json
- Set the method to
POST
- Give the connection a name eg
Shopify Orders
- Complete Authorization as per above if you have not already
- Select
Shopify Login (Oauth)
authentication
- Select your Shopify store (will only show if you’ve completed authorization)
- 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.


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 } } } }
- Enter your graphQL code into the Body setting of the
- Click Preview
- Preview window will open and show you something like

Try clicking the
View raw data
toggle 
And you’ll see data like:

SyncWith lets you automatically handle your JSON expansions by clicking the + expand button

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
Shipping the Data into Google Sheets
To insert the order data into Google Sheets simply click Insert

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 reverse:true") { edges { node { id } } } }
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 sortKey:TOTAL_PRICE) { edges { node { id } } } }
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
.gif)
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
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 |
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:iphoneAND
NOT
source_name:android
status:cancelled OR status:closed
Comparators
:
equality
:<
less than
:>
greater than
:<=
less than or equal to
:>=
greater than or equal toA 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:
- Send the query to Shopify
- Get x results
- Get the ID of the last result
- Send another query and ask for the x more items starting after the last result (using the connection argument
after
)
- 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
eyJsYXN0X2lkI
... 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


- Above we tested a fixed cursor
after:"eyJsYXN0X2lkI..."
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
- typeMailingAddress
Order.currentTotalPriceSet
- typeMoneyBag
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{name address1 address2 city country} } } }
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
. .gif)
Whereas Order.LineItems takes many more arguments:
first
after
last
before
reverse

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 lineItems(first:10){edges{node{ id}}} #first 10 lineitems for each order } } } }
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 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 }}} } } } }
and the results:
{ "data": { "orders": { "edges": [ { "node": { "id": "gid://shopify/Order/1379031122002", "lineItems": { "edges": [ { "node": { "id": "gid://shopify/LineItem/3010689663058", "sku": "", "quantity": 1, "name": "Glass Head Pins", "discountedTotalSet": { "presentmentMoney": { "amount": "8.0" } } } }, { "node": { "id": "gid://shopify/LineItem/3010689695826", "sku": "", "quantity": 1, "name": "Hot Hemmer", "discountedTotalSet": { "presentmentMoney": { "amount": "23.3" } } } } ] } } } ] }
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:

We can then click expand a few times and get our JSON converted to a table format:

And if we want we can use SyncWith to move this data to our platform of choice, in this case Google Sheets:

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": { "requestedQueryCost": 37, "actualQueryCost": 33, "throttleStatus": { "maximumAvailable": 1000, "currentlyAvailable": 951, "restoreRate": 50 } } }
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
requestedQueryCost
≤currentlyAvailable
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 lowactualQueryCost
you can run into the issue where you can’t run queries even thoughactualQueryCosts
≤currentlyAvailable
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 ordersLets 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:
.gif)
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
- Reach out to us at https://syncwith.com/support