Skip to content

Offset vs Cursor Pagination ​

For a deeper comparison of consistency, performance, and Prisma implementation choices, read Cursor vs Offset Pagination in Prisma.

Query Parameters ​

Offset ​

GET /users?page=2&limit=20&sortBy=createdAt:DESC&search=john&filter.role=$eq:admin
ParamDescriptionExample
pagePage number (1-based)2
limitItems per page20
sortBySort (multi allowed)createdAt:DESC
searchFull-text searchjohn
filter.{col}Filter by columnfilter.role=$eq:admin

Cursor ​

GET /users?limit=20&after=eyJpZCI6IjEwIn0&sortBy=createdAt:DESC
ParamDescriptionExample
limitItems per page20
afterForward cursor (Base64url)eyJpZCI6IjEwIn0
beforeBackward cursoreyJpZCI6NX0
sortBySortcreatedAt:DESC

Cursor mode activates automatically when after/before is present or paginationType: 'cursor' is set.

Keyset Cursors for Non-Unique Ordering ​

For a feed sorted by createdAt, add a unique id tie-breaker and select the keyset strategy:

typescript
const result = await paginate(query, this.prisma.user, {
  sortableColumns: ['createdAt', 'id'],
  paginationType: 'cursor',
  cursorStrategy: 'keyset',
  cursorColumns: ['createdAt', 'id'],
  defaultSortBy: [['createdAt', 'DESC'], ['id', 'DESC']],
});

Use a matching index such as @@index([createdAt(sort: Desc), id(sort: Desc)]). Keep cursor columns populated and stable, preserve the sort and filters, and follow result.links.next instead of constructing a cursor manually. The unique tie-breaker distinguishes records with equal timestamps; it does not create a snapshot across requests. See the worked SQL example and consistency limits.

Response Format ​

Offset ​

json
{
  "data": [{ "id": "1", "name": "Alice" }],
  "meta": {
    "itemsPerPage": 20,
    "totalItems": 500,
    "currentPage": 1,
    "totalPages": 25,
    "sortBy": [["createdAt", "DESC"]]
  },
  "links": {
    "first": "/users?page=1&limit=20&sortBy=createdAt%3ADESC",
    "previous": null,
    "current": "/users?page=1&limit=20&sortBy=createdAt%3ADESC",
    "next": "/users?page=2&limit=20&sortBy=createdAt%3ADESC",
    "last": "/users?page=25&limit=20&sortBy=createdAt%3ADESC"
  }
}

Cursor ​

json
{
  "data": [{ "id": "10", "name": "Bob" }],
  "meta": {
    "itemsPerPage": 20,
    "hasNextPage": true,
    "hasPreviousPage": true,
    "startCursor": "eyJpZCI6IjEwIn0",
    "endCursor": "eyJpZCI6IjI5In0",
    "sortBy": [["createdAt", "DESC"]]
  },
  "links": {
    "current": "/users?limit=20&after=eyJpZCI6IjEwIn0",
    "next": "/users?limit=20&after=eyJpZCI6IjI5In0",
    "previous": "/users?limit=20&before=eyJpZCI6IjEwIn0"
  }
}

Released under the MIT License.