Pagination & Fields
Cursor pagination, pageInfo, field projection, and infinite queries
Beyond limit / offset (covered in Basics), InstantDB supports cursor
pagination, a pageInfo cursor summary, field projection, and an accumulating infinite
query.
Cursor pagination
Under a namespace's $ options, use first / after for forward paging and
last / before for backward paging. afterInclusive / beforeInclusive control
whether the cursor row itself is included.
// First page: leading 2 by ascending order
final firstPage = await db.queryOnce({
'todos': {
'\$': {'order': {'n': 'asc'}, 'first': 2},
},
});pageInfo
When a query paginates, QueryResult.pageInfo carries a per-namespace cursor summary:
startCursor, endCursor, hasNextPage, and hasPreviousPage. It is null when the
query did not paginate.
final r = await db.queryOnce({
'todos': {
'\$': {'order': {'n': 'asc'}, 'first': 2},
},
});
r.pageInfo?['todos']?['hasNextPage']; // true
r.pageInfo?['todos']?['hasPreviousPage']; // false
final cursor = r.pageInfo?['todos']?['endCursor'] as String;Use endCursor as the after value to fetch the next page:
final next = await db.queryOnce({
'todos': {
'\$': {'order': {'n': 'asc'}, 'first': 2, 'after': cursor},
},
});Field projection
Use fields under $ to fetch only specific attributes. The id is always included.
final r = await db.queryOnce({
'todos': {
'\$': {'fields': ['title', 'status']},
},
});Infinite queries
db.infiniteQuery(...) returns an accumulator that concatenates items across pages and
advances via loadMore(). pageSize becomes the first count; entityType is the
namespace to paginate. Pair it with the InstantInfiniteBuilder widget.
final inf = db.infiniteQuery(
{
'todos': {
'\$': {'order': {'n': 'asc'}, 'first': 2},
},
},
pageSize: 2,
entityType: 'todos',
);
// inf.items -> Signal<List<Map<String, dynamic>>> (accumulated)
// inf.hasMore -> Signal<bool>
// inf.isLoading -> Signal<bool>
await inf.loadMore(); // append the next page
// Dispose when done
inf.dispose();InstantInfiniteBuilder(
query: inf,
builder: (context, items, hasMore) {
return ListView.builder(
itemCount: items.length + (hasMore ? 1 : 0),
itemBuilder: (context, index) {
if (index >= items.length) {
inf.loadMore();
return const Center(child: CircularProgressIndicator());
}
return TodoTile(todo: items[index]);
},
);
},
)Per-relation pageInfo
Cursor-paginated nested include relations surface their own pageInfo under a
composite key. See Typed Relations for
details — the key looks like result.pageInfo?['goals.todos'].
Typed equivalent
The typed query DSL exposes
first / last / after / before / afterInclusive / beforeInclusive and
select as type-checked builder methods, returning the same QueryResult with the
same pageInfo.