Use pagination to access all GraphQL query results

Paging the results of GraphQL queries.

Skedulo GraphQL provides multiple ways to control the number of results returned in queries. You can access additional data by using pagination.

Using the limit parameter

The limit parameter allows you to request up to 10,000 top-level records, with dynamic limits applied based on query complexity to protect system performance.

Dynamic query limits: The maximum value for the limit parameter is calculated dynamically based on query complexity to protect system performance:

  • Default: 200 if not specified
  • Header: The calculated limit is returned in the X-Skedulo-Dynamic-Query-Limit response header

Subquery limits (for HasMany relationships when using limit):

  • Maximum: 2000
  • Default: 20
  • Automatic: When using top-level limit, all HasMany relationships automatically have a limit parameter applied (defaults to 20 if not specified)
  • No pagination: Unlike top-level queries, subqueries on HasMany relationships do not support pagination. Refer to “Subquery Limits and Pagination” section below for more detail.

It is recommended that you explicitly specify the sorting direction of the orderBy parameter/s by using either ASC for ascending order of results or DESC for descending order, for example: orderBy: Start DESC, UID ASC. To use pagination reliably, you should ensure that your query includes an orderBy: UID parameter so that the result set is stable. In most cases, the sort order direction of the UID is not relevant.

Subquery limits and pagination

Unlike top-level queries, subqueries on HasMany relationships do not support pagination. This means you must carefully consider the limit parameter for subqueries to ensure you retrieve all the data you need. If you expect many records to be returned by the subquery, consider implementing an independent query for that data.

Guidelines for setting subquery limits

  1. Understand your data: Analyze your data to understand the typical number of related records
  2. Use filters: Apply filters to subqueries to reduce the number of records returned
  3. Set realistic limits: Choose a limit that covers the maximum number of records you expect, but not so high that it significantly impacts the top-level dynamic query limit
  4. Consider data growth: Account for potential data growth when setting limits

Example: Balancing top-level and subquery limits

{
  jobs(limit: 800) {
    edges {
      node {
        UID
        Name
        JobStatus
        JobAllocations(limit: 15, filter: "Status != 'Deleted'") {
          UID
          Status
        }
        JobOffers(limit: 8) {
          UID
          Status
        }
      }
    }
  }
}

In this example:

  • The top-level query requests up to 800 jobs
  • Each job can have up to 15 JobAllocations (no pagination available)
  • Each job can have up to 8 JobOffers (no pagination available)
  • If a job has more than 15 allocations or 8 offers, the excess records will not be returned

If you need more records than the subquery limit allows, consider these approaches:

  1. Increase the subquery limit (up to the maximum of 2000)
  2. Use separate queries to fetch related data
  3. Apply filters to reduce the result set
  4. Restructure your query to fetch data at the top level instead

Pagination support

Relay cursor pagination

Skedulo Lens GraphQL pagination support uses the Relay Cursor Connections Specification.

For example, the following query for jobs returns a node for each job consisting of the UID and Name along with an opaque cursor.

{
  jobs(orderBy: "UID") {
      edges {
        cursor
        node {
          UID
          Name
        }
      }
      pageInfo {
        hasNextPage
      }
    }
}

This query would return these results (some data omitted for simplicity):

{
  "data": {
    "jobs": {
      "edges": [
        {
          "cursor": "MQ==",
          "node": {
            "UID": "a0E6F00001dWnbrUAC",
            "Name": "JOB-0033"
          }
        },
        {
          "cursor": "Mg==",
          "node": {
            "UID": "a0E6F00001dWnbhUAC",
            "Name": "JOB-0032"
          }
        }      
      ],
      "pageInfo": {
        "hasNextPage": true
      }
    }
  }
}

Note that this fetches each entry with a cursor. The last entry pageInfo indicates that there is more data to be fetched. This can be done by supplying the cursor of the last item as an argument to the jobs query.

For example:

{
  jobs(after: "Mg==", orderBy: "UID") {
      edges {
        cursor
        node {
          UID
          Name
        }
      }
      pageInfo {
        hasNextPage
      }
    }
}

Would give the result:

{
  "data": {
    "jobs": {
      "edges": [
        {
          "cursor": "Mw==",
          "node": {
            "UID": "a0E6F00001BiaRWUAZ",
            "Name": "JOB-0007"
          }
        },
        {
          "cursor": "NA==",
          "node": {
            "UID": "a0E6F00001BiaRbUAJ",
            "Name": "JOB-0008"
          }
        }
      ],
      "pageInfo": {
        "hasNextPage": false
      }
    }
  }
}

You can also limit the number of items returned in the query using the limit parameter to the “jobs” query.

Example with limit parameter

{
  jobs(limit: 1200, orderBy: "UID") {
    edges {
      cursor
      node {
        UID
        Name
        JobAllocations(limit: 25) {
          UID
          Status
        }
      }
    }
    pageInfo {
      hasNextPage
    }
  }
}

This query will fetch up to 1,200 job records, with each job record limited to a maximum of 25 JobAllocation records. Note that the JobAllocations subquery does not support pagination, so you must select an appropriate limit that covers all the records you need.

See the Relay Cursor Connections Specification for full details of the supported operations.

Start and offset pagination

In addition to the cursor-based paging, Skedulo’s GraphQL implementation also supports using offsets to page results. This can be useful when you want to show pagination controls that allow you to jump ahead in a number of pages, by allowing you to divide into multiples and have the query fetch that many results and render them. To do this we use the offset parameter to indicate which record the pages should be fetched from and the limit parameter to indicate the number of records to fetch, for example:

query  {
  jobs(limit: 3, offset: 0, orderBy: "UID") {
    edges {
      offset
      node {
        UID
        Name
        Description
        JobStatus
      }
    }
  }
}

Returns the result:

{
  "data": {
    "jobs": {
      "edges": [
        {
          "offset": 0,
          "node": {
            "UID": "0014151a-404c-43bd-ab69-444cf0531e93",
            "Name": "JOB-10",
            "Description": "Stuff 10",
            "JobStatus": "Queued"
          }
        },
        {
          "offset": 1,
          "node": {
            "UID": "0014daaf-ea46-48f3-be2b-13045a953307",
            "Name": "JOB-11",
            "Description": "Stuff 11",
            "JobStatus": "Queued"
          }
        },
        ...
    ]
	}
}

Salesforce pagination limitation

Salesforce imposes strict pagination limits that affect how you can access large datasets:

  • Maximum offset: 2,000 records from the beginning of the result set
  • Maximum total records: 2,000 records when using pagination (cursor or offset-based)

These limitations apply to both cursor-based and offset-based pagination methods.

Example scenarios

Scenario 1: Exceeding the offset limit

# Query 1: Fetch initial results
{
  jobs(limit: 10000, orderBy: "UID") {
    edges {
      cursor
      node {
        UID
        Name
      }
    }
    pageInfo {
      hasNextPage
    }
  }
}

This query returns 10,000 records with a cursor at position 10,000.

# Query 2: Attempt to continue pagination
{
  jobs(limit: 500, orderBy: "UID", after: "MTAwMDA=") {
    edges {
      cursor
      node {
        UID
        Name
      }
    }
    pageInfo {
      hasNextPage
    }
  }
}

Result: Query 2 fails because it attempts to access records at offset 10,000, which exceeds Salesforce’s 2,000 record limit.

Scenario 2: Exceeding the total record limit

{
  jobs(limit: 3000, orderBy: "UID", after: "NTAw") {
    edges {
      cursor
      node {
        UID
        Name
      }
    }
    pageInfo {
      hasNextPage
    }
  }
}

Result: This query returns only 2,000 records (not the requested 3,000) because Salesforce limits the total number of records when using pagination.

Workarounds for large datasets

When working with datasets larger than 2,000 records on Salesforce:

  1. Use filters to reduce the result set size
  2. Implement data partitioning by date ranges, UID or other criteria
  3. Use multiple queries with different filter conditions
  4. Consider data export for bulk operations