Paginating Deta Base in Deta Space: Cursor Keys, Index Rules, and When to Switch to Postgres
Deta Base pagination is cursor-based and index-backed. Learn how to page with limit and last, why unindexed queries fail, and when to move to an external Postgres.
16 Apr 2026, 03:24 UTC

You have a Deta Space app with a Deta Base collection that has grown past a few hundred records. You need to show a list in the UI, or process every item in a scheduled job. The habits you brought from SQL—ask for page 3, ask for the total count—do not exist here. The useful takeaway: Deta Base pagination is cursor-based and index-backed. You design the index first, page with limit and last, keep pages bounded, and if you need joins, ad-hoc filters, or offset paging, plan an external database before the data model hardens.
Index design comes before pagination
Deta Base is a managed key-value/document store available inside Space apps through the Node.js and Python SDKs. Before you write a single page request, you must decide which fields the query filters or sorts on, because Base queries have to be satisfied by an index defined on the collection—single-field or compound. An unindexed query is rejected, so pagination design starts with index design.
Practically, this means you cannot treat Base like a schemaless bucket and sort by whatever field the UI happens to need today. If a new access pattern appears later, you add the index first, then paginate against it. Skipping this step produces runtime errors that look like pagination bugs but are actually missing index definitions.
Cursor pagination with limit and last
Base reads use db.get() or Base.query(), both of which accept limit and last. The last parameter is the key of the final item in the previous response; passing it back tells Base where the next page starts. There is no OFFSET and no COUNT API, so you cannot jump to an arbitrary page number or report a total result count from the query itself.
Ordering matters. Results come back in ascending key order by default, and desc=True reverses that. The last cursor follows whichever ordering you chose, so switching sort direction in the middle of a pagination sequence silently breaks the walk. Pick one direction per pagination flow.
Because there is no offset, UI patterns that expect stable page numbers are a poor fit. Load-more buttons and infinite scroll work naturally; “jump to page 7” does not. If your product requirements demand totals or deep linking to a page, that is an early signal to look outside Base.
Worked example: a bounded pagination loop
The following Node.js pattern shows the shape of a resumable scan. It is written to be copied into a Space action or cron handler, not run as-is. Replace the placeholders with your collection name and verify the exact SDK method names against the current Deta documentation.
// Run inside a Deta Space app action or scheduled cron handler.
// Requires the Deta SDK initialized for your Space project.
// Permissions: the app's Base key must allow reads on <collectionName>.
async function scanCollection(<collectionName>, pageSize = 200) {
let lastKey = undefined;
let totalSeen = 0;
while (true) {
// Fetch one bounded page. Adjust the query object to match your SDK version.
const page = await db.get(<collectionName>, {
limit: pageSize,
last: lastKey,
// desc: false // keep ordering fixed for the whole loop
});
const items = page.value || [];
if (items.length === 0) break;
// Expected check: the first key of this page should not appear in the previous page.
// Expected check: no duplicate keys across pages.
for (const item of items) {
// process item
}
totalSeen += items.length;
lastKey = items[items.length - 1].key;
// If the page is shorter than pageSize, you have reached the end.
if (items.length < pageSize) break;
}
return totalSeen;
}
Two risks to watch. First, oversized pages risk timeouts and throttling under per-app quotas, so a bounded page size—roughly a few hundred items—is safer than asking for everything at once. Second, if this loop runs in a cron or is triggered by Base events, make it idempotent and resumable: store the lastKey in a separate state record so a throttled or failed run can pick up where it stopped rather than replaying the whole collection.
Trade-offs and the Postgres boundary
Base is a good fit for small-to-medium app data with simple, index-backed access patterns. It becomes the wrong tool when your queries outgrow that shape.
| Need | Deta Base | External managed Postgres |
|---|---|---|
| Key lookups and simple indexed scans | Native fit | Works, but more operational surface |
Cursor pagination with limit/last | Native fit | Also possible with keyset pagination |
Offset pagination or COUNT(*) | Not available | Native fit |
| Joins across collections | Not available | Native fit |
| Relational transactions | Not available | Native fit |
| Ad-hoc query patterns added late | Requires new index first | Flexible within reason |
If you choose Base, model denormalized documents and handle consistency in application code. If you choose Postgres, you accept connection management and migration overhead inside a Space app, but you gain offset paging, totals, joins, and transactions.
One more caution: Deta has sunset standalone Deta Base and Drive in favor of Space. Before committing, confirm current availability, limits, and pricing in Deta’s official documentation and changelog, and verify that the index rules and pagination parameters described here still match the shipped SDK.
Actionable next steps
- Scaffold a Deta Space app with the Space CLI or the browser Builder, add a Base, and insert a few hundred test records.
- Define the index your query needs before running the pagination loop.
- Run the
limit/lastloop against the real collection and confirm each page resumes from the previous cursor with no duplicates. - Measure latency and quota usage at your chosen page size, then adjust
limitto stay comfortably under response caps. - If requirements include joins, transactions, or offset paging, prototype the external Postgres path before the Base schema hardens.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.