Edit

Share via


Pagination with $after in REST

Pagination narrows large datasets to smaller, manageable pages. In REST, Data API builder (DAB) uses the $after query parameter for keyset pagination, providing stable and efficient traversal through ordered results. Each token marks the position of the last record from the previous page, allowing the next request to continue from that point. Unlike offset pagination, keyset pagination prevents missing or duplicated rows when data changes between requests.

Go to the GraphQL version of this document.

Quick glance

Concept Description
$after The opaque continuation token returned from the prior request
$first The maximum number of records to fetch per page
nextLink URL for the next page includes $after

Basic pagination

In this example, we're getting the first three books.

HTTP request

GET /api/books?$first=3

Conceptual SQL

SELECT TOP (3)
  id,
  sku_title AS title
FROM dbo.books
ORDER BY id ASC;

Sample response

{
  "value": [
    { "id": 1, "title": "Dune" },
    { "id": 2, "title": "Foundation" },
    { "id": 3, "title": "Hyperion" }
  ],
  "nextLink": "/api/books?$first=3&$after=eyJpZCI6M30="
}

Note

If next-link-relative=true in configuration, nextLink contains a relative path; otherwise, it’s an absolute URL.

Continuation with $after

The $after parameter specifies the continuation token for the next page. The value is a base64-encoded string representing the last record of the previous page.

Warning

$after carries an opaque token that identifies where the last page ended. Treat tokens as immutable and never try to construct or modify them.

In this example, we're getting the next three books after the last page’s token.

HTTP request

GET /api/books?$first=3&$after=eyJpZCI6M30=

Conceptual SQL

SELECT TOP (3)
  id,
  sku_title AS title
FROM dbo.books
WHERE id > 3
ORDER BY id ASC;

Sample response

{
  "value": [
    { "id": 4, "title": "I, Robot" },
    { "id": 5, "title": "The Left Hand of Darkness" },
    { "id": 6, "title": "The Martian" }
  ],
  "nextLink": "/api/books?$first=3&$after=eyJpZCI6Nn0="
}

End of data

When nextLink is absent, there are no more records to fetch. The final page response includes only a value array without a nextLink.

Sample response

{
  "value": [
    { "id": 7, "title": "Rendezvous with Rama" },
    { "id": 8, "title": "The Dispossessed" }
  ]
}

Note

Any schema or ordering change invalidates previously issued tokens. Clients must restart pagination from the first page.

Example configuration

{
  "runtime": {
    "pagination": {
      "default-page-size": 100,
      "max-page-size": 100000
    }
  },
  "entities": {
    "Book": {
      "source": {
        "type": "table",
        "object": "dbo.books"
      },
      "mappings": {
        "sku_title": "title",
        "sku_price": "price"
      },
      "relationships": {
        "book_category": {
          "cardinality": "one",
          "target.entity": "Category",
          "source.fields": [ "category_id" ],
          "target.fields": [ "id" ]
        }
      }
    },
    "Category": {
      "source": {
        "type": "table",
        "object": "dbo.categories"
      },
      "relationships": {
        "category_books": {
          "cardinality": "many",
          "target.entity": "Book",
          "source.fields": [ "id" ],
          "target.fields": [ "category_id" ]
        }
      }
    }
  }
}

See also

Concept REST GraphQL Purpose
Projection $select items Choose which fields to return
Filtering $filter filter Restrict rows by condition
Sorting $orderby orderBy Define the sort order
Page size $first first Limit the number of items per page
Continuation $after after Continue from the last page using a cursor

Note

REST keywords begin with $, following OData conventions.