← Back to Docs

Recipe: Cursor-based pagination design

Replace offset/limit with stable cursors for predictable, performant list traversal.

Why cursors

Offset pagination drifts when rows are inserted or deleted between requests. Cursors anchor each page to a stable sort key, so the client always sees a consistent slice — no duplicates, no gaps.

Cursor shape

Encode the last-seen sort value into an opaque base64 token. For timestamp-ordered lists use created_at plus a tie-breaker like id. Never expose internal row offsets.

Request / response

Clients send ?cursor=<token>&limit=50. The server decodes the cursor, applies a WHERE clause on the sort key, and returns the next batch plus a next_cursor field. Omit next_cursor when the batch is smaller than limit to signal end-of-list.

Forward-only by default

Bidirectional cursors add complexity. Start with forward-only pagination. If reverse traversal is needed later, add a prev_cursor and flip the sort direction.

Meridian tip: Store cursors as HMAC-signed tokens to prevent clients from tampering with sort values. Validate the signature on every request before decoding.