FaunaDB Index‑Based Pagination: Design, Usage, and Common Pitfalls
FaunaDB’s cursor‑based pagination lets you fetch pages without offsets. Learn how to design a paginated index, run <code>Paginate</code> queries, and avoid common pitfalls like cursor size limits and non‑unique sort fields.
08 Nov 2025, 20:49 UTC

Why Use Index‑Based Pagination?
FaunaDB’s Paginate function returns a Page object that includes after and before cursors. These cursors are deterministic and can be used for offset‑free pagination, avoiding the performance hit of large offset values.
Designing a Paginated Index
To paginate, you need an index that orders the data by a field you can use as a cursor. The index must expose the reference (ref) of each document so that the cursor can uniquely identify the next item. A typical index on a User collection sorted by createdAt looks like this:
CreateIndex({
name: "users_by_created",
source: Collection("User"),
values: [{ field: ["ref"] }],
terms: [{ field: ["createdAt"] }],
serialized: true
});
Key points:
valuesincludesrefso the cursor can reference a specific document.- Ordering is implicit because
termsare sorted lexicographically. serialized: trueensures the cursor is a string that can be stored client‑side.
Paginate Forward
Once the index is ready, you can fetch pages with Paginate. The size controls how many items per page, and the after cursor tells FaunaDB where to start.
Paginate(
Match(Index("users_by_created")),
{ size: 10, after: <cursor> }
);
Initial request uses after: null or omits the field. The response contains:
| Field | Description |
|---|---|
| items | Array of ref objects (or full documents if values includes more fields). |
| after | Cursor for the next page. |
| before | Cursor for the previous page (useful if you add a before parameter). |
| cost | Query cost in FaunaDB units. |
Concrete Example Flow
- Create the
Usercollection and insert 50 documents with acreatedAttimestamp. - Deploy the
users_by_createdindex via the console or SDK. - Run the first query:
Paginate(Match(Index("users_by_created")), { size: 10 }); - Store the returned
aftercursor. - For the next page, use that cursor:
Paginate(Match(Index("users_by_created")), { size: 10, after: <cursor> }); - Verify that the second page contains the next 10 documents and that the
aftercursor changes.
Limitations and Common Mistakes
- Cursor Size: The cursor is capped at 1 KB. Complex indexes with many fields can exceed this limit, causing the query to fail.
- Non‑Unique Sort Field: If
createdAtis not unique, multiple documents share the same cursor value. Without includingrefin thevaluesclause, pages may overlap or skip items. - Large
sizeValues: Requests withsize> 1000 can trigger throttling. Keepsizewithin 100–200 for most use cases. - Using
beforefor Backward Pagination: Thebeforecursor only works reliably if the index is ordered by a unique field. Otherwise, you might see duplicate or missing records. - Ignoring Query Cost: Each page costs units. For indexes that scan many documents, the cost can grow quickly. Monitor the
costfield and adjustsizeor index design accordingly.
Practical Checks
- After each pagination request, inspect the
costfield. A cost > 1 per page indicates the index may be scanning many documents. - Verify continuity by comparing the last
refof page N with the firstrefof page N+1. They should be adjacent in the index order. - When using non‑unique fields, test with a small dataset to ensure no duplicates appear across pages.
When to Use This Pattern
Index‑based pagination is ideal for:
- Read‑heavy APIs where offset‑based pagination would become expensive as the dataset grows.
- Applications that need stable ordering (e.g., chronological feeds).
- Scenarios where you can preserve the
aftercursor between client requests.
Conclusion
By creating a well‑structured index that exposes the document reference and ordering field, you can leverage FaunaDB’s cursor‑based Paginate to build efficient, offset‑free pagination. Remember the cursor size, non‑unique field pitfalls, and query cost when scaling your application.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.