Cursor
Cursor implements cursor-based (keyset) pagination. Instead of skipping an arbitrary number of rows like Skip, you position the query after a specific record, identified by a unique predicate such as user.Email.EQ(...) or user.Id.EQ(...).
This approach stays stable under concurrent inserts and updates and is far more efficient on large datasets than offset pagination.
Cursor can be used with FindFirst, FindMany, and nested relation query builders. It is typically combined with OrderBy and Take.
Fetching the Next Page
Combine a cursor with a positive Take to fetch the page following a known record:
nextPage, err := db.User.
FindMany(user.Email.Contains("@example.com")).
OrderBy(user.Email.Asc()).
Cursor(user.Email.EQ(lastEmail)).
Take(20).
Exec(ctx)This returns the next 20 records after lastEmail.
Fetching the Previous Page
Combine a cursor with a negative Take to fetch the records that precede it:
prevPage, err := db.User.
FindMany(user.Email.Contains("@example.com")).
OrderBy(user.Email.Asc()).
Cursor(user.Email.EQ(firstEmailOfCurrentPage)).
Take(-20).
Exec(ctx)Note: The cursor value must reference a unique field, such as the record's
Idor another unique column.
Usage Tips
- Always provide a stable
OrderBy; it must be consistent from page to page. - Pass the last record of the current page as the cursor for the next page, and the first record for the previous page.
- Use
Skiptogether with a cursor when you want to jump a fixed number of rows past the cursor.
Supported By
Resulting SQL
A cursor query compiles into a key-based comparison on the cursor's column, combined with the ordering and limit:
SELECT * FROM "users"
WHERE "email" > 'last@example.com'
ORDER BY "email" ASC
LIMIT 20;A negative Take swaps the comparison direction to fetch preceding rows.