Paginating results with GraphQL
When you use a connection to retrieve a list of resources, you use arguments to specify the number of results to retrieve. You can select which set of results to retrieve from a connection by using cursor-based pagination.
A review of connections
Connections retrieve a list of nodes. A node is an object that has a global ID and is of a type that's defined by the schema, such as the Order
type. Nodes are said to be connected by edges.
For example, the orders
connection finds all the edges that connect the query root to a Order node, and then returns the requested data from each node. You can even nest connections within other connections, such as using the lineItems
connection to retrieve the line items for each order in the orders
connection.
To paginate the list of orders, you need to return data that isn't included on the Order type. This data includes whether there are more results in the connection, and which orders are included in the current results. To let you select fields that aren't on the node object, Shopify includes the edges
and node
layers in every connection:
query {
orders(first:2) {
edges {
node {
id
}
}
}
}
{
"data": {
"orders": {
"edges": [
{
"node": {
"id": ...
}
},
{
"node": {
"id": ...
}
}
]
}
}
}
- edges — Used to select the fields that you want to retrieve from each edge in the connection. The
edges
field is similar to a for-loop because it retrieves the selected fields from each edge in the connection. - node — Used to select the fields that you want to retrieve from the node at each edge.
Specifying fields at the connection, edge, and node levels lets you include fields that are used for pagination: pageInfo
and cursor
.
query {
orders(first:10) {
pageInfo { # Returns details about the current page of results
hasNextPage # Whether there are more results after this page
hasPreviousPage # Whether there are more results before this page
}
edges {
cursor # A marker for an edge's position in the connection
node {
name # The fields to be returned for each node
}
}
}
}
The pageInfo field
Each connection can return a PageInfo object, which has two fields:
hasNextPage
(Boolean!) — Whether there are results in the connection after the current segment.hasPreviousPage
(Boolean!) — Whether there are results in the connection before the current segment.
You retrieve page information once per query rather than at each edge, so include pageInfo
at the connection level beside the edges
field:
POST /admin/api/2021-04/graphql.json
query {
products(first:3) {
pageInfo {
hasNextPage
hasPreviousPage
}
edges {
node {
id
}
}
}
}
JSON response
{
"data": {
"products": {
"pageInfo": {
"hasNextPage": true,
"hasPreviousPage": false
},
"edges": [
{
"node": {
"id": ...
}
},
{
"node": {
"id": ...
}
},
{
"node": {
"id": ...
}
}
]
}
},
...
}
The cursor field
Each edge in a connection can return a cursor, which is a reference to the edge's position in the connection. The cursor is a property of the edge rather than the node, so include the cursor
field at the edge level beside the node
field:
query {
orders(first:10) {
edges {
cursor
node {
id
}
}
}
}
You can use an edge's cursor as the starting point to retrieve the nodes before or after it in a connection.
Cursor-based pagination
You can include the pageInfo
and cursor
fields in your queries to paginate your results. The following example includes both fields, and uses query variables to pass their values as arguments. The $orderNum
variable is required, and is used to specify the number of results to return. The $cursor
variable isn't required. When the $cursor
variable is missing, the after
argument is ignored.
POST /admin/api/2021-04/graphql.json
query ($numProducts: Int!, $cursor: String){
products(first: $numProducts, after: $cursor) {
pageInfo {
hasNextPage
hasPreviousPage
}
edges {
cursor
node {
title
}
}
}
}
Variables
{
"numProducts": 3
}
JSON response
{
"data": {
"products": {
"pageInfo": {
"hasNextPage": true,
"hasPreviousPage": false
},
"edges": [
{
"cursor": "eyJsYXN0X2lkIjoxMDA3OTc4ODg3NiwibGFzdF92YWx1ZSI6IjEwMDc5Nzg4ODc2In0=",
"node": {
"title": "The T-Shirt"
}
},
{
"cursor": "eyJsYXN0X2lkIjoxMDA3OTc5MzQyMCwibGFzdF92YWx1ZSI6IjEwMDc5NzkzNDIwIn0=",
"node": {
"title": "The Backpack"
}
},
{
"cursor": "eyJsYXN0X2lkIjoxMDA3OTc5NDM4MCwibGFzdF92YWx1ZSI6IjEwMDc5Nzk0MzgwIn0=",
"node": {
"title": "The Blouse"
}
}
]
}
},
...
}
The query returns three results from the beginning of the connection, and they include a cursor for each edge.
In the next example, the cursor from The Blouse
is used as the starting point for the connection results. The example retrieves the next three results after that point in the connection:
POST /admin/api/2021-04/graphql.json
query ($numProducts: Int!, $cursor: String){
products(first: $numProducts, after: $cursor) {
pageInfo {
hasNextPage
hasPreviousPage
}
edges {
cursor
node {
title
}
}
}
}
Variables
{
"numProducts": 3,
"cursor": "eyJsYXN0X2lkIjoxMDA3OTc5NTc4OCwibGFzdF92YWx1ZSI6IjEwMDc5Nzk1Nzg4In0="
}
JSON response
{
"data": {
"products": {
"pageInfo": {
"hasNextPage": true,
"hasPreviousPage": true
},
"edges": [
{
"cursor": "eyJsYXN0X2lkIjoxMDA3OTc5NjMwMCwibGFzdF92YWx1ZSI6IjEwMDc5Nzk2MzAwIn0=",
"node": {
"title": "The Shorts"
}
},
{
"cursor": "eyJsYXN0X2lkIjoxMDA3OTc5NjY4NCwibGFzdF92YWx1ZSI6IjEwMDc5Nzk2Njg0In0=",
"node": {
"title": "The Jumper"
}
},
{
"cursor": "eyJsYXN0X2lkIjoxMDA3OTc5NzE5NiwibGFzdF92YWx1ZSI6IjEwMDc5Nzk3MTk2In0=",
"node": {
"title": "The Sneakers"
}
}
]
}
},
...
}
Next steps
- Advanced GraphQL concepts — Develop your GraphQL skills even further with these advanced concepts.