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 Ordertype. 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/2019-10/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": ...
          }
        }
      ]
    }
  },
  ...
}

Run in GraphiQL

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/2019-10/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"
          }
        }
      ]
    }
  },
  ...
}

Run in GraphiQL

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/2019-10/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"
          }
        }
      ]
    }
  },
  ...
}

Run in GraphiQL

Next steps

Sign up for a Partner account to get started.

Sign up