Deta Space Collections After the Shutdown: What the Per-User Document Store Got Right
Deta Space shut down in 2024, taking its zero-config Collections store with it. What the per-user design got right, where it hit limits, and what to do with old deta SDK code today.
09 May 2026, 11:35 UTC

If you have inherited a repository that imports the deta SDK, or you are designing storage for a personal-cloud app and keep running into references to Deta Space, the answer you need first is this: the platform shut down in 2024. Collections — its built-in document store — no longer accepts traffic, no migration path was offered after the deadline, and the SDKs are unmaintained. Old code cannot be pointed at a live service.
What is still worth studying is the design. Collections made one decision unusually well: it treated data ownership as a property of the platform rather than a problem for the developer. Each user of an app got their own isolated copy of every collection, with credentials injected at runtime, so a solo developer could ship a notes app without writing tenancy logic, provisioning a database, or managing tokens. This piece explains how that worked, shows the code pattern it produced, and catalogs the limits that eventually mattered — useful as history, and as a checklist if you are building something similar.
What Collections actually was
Collections was a schema-less document store bundled with every Space app. "Schema-less" means you stored JSON-style documents without declaring columns or types up front; each document was identified by a key — one you supplied, or one the service generated. Under the SDK, everything was plain HTTPS calls to a collections endpoint scoped to the app (a path of the form /_collections/<name>), so the SDK was a thin wrapper rather than a driver with a connection pool.
Two properties defined the model:
- Per-user isolation. Deta Space ran one instance of your app per user, and each instance's collections were its own. User A's notes were never in the same dataset as user B's. There was no tenant ID to filter by, because there were no other tenants in your dataset.
- Runtime-injected auth. The platform set a
DETA_SPACE_APP_TOKENenvironment variable inside the app's runtime, and the SDK read it automatically. App code never handled credentials, and there were no connection strings to leak.
Deployment used the space CLI from the app repository root: space push packaged the app as a container image and ran it on Deta's MicroVM fleet — small virtual machines, one per app instance — with the collections endpoint wired in. space dev ran the same container locally against a mock collections server.
The pattern in code: a notes app
The SDK surface was five methods per collection: put, get, delete, fetch, and update. The canonical Python pattern looked like this. It is illustrative of the final-era deta package, not run against anything — the service no longer exists.
from deta import Deta
db = Deta().Base('notes')
# store a document; a key is generated if you don't supply one
db.put({'title': 'Idea', 'body': 'Zero-config storage'})
# filter with the ?suffix operators
result = db.fetch({'title?contains': 'Idea'})
for item in result.items:
print(item['key'], item['title'])
# result.last holds a cursor when more pages remainFilters supported comparison and text operators (eq, gt, gte, lt, lte, contains, startsWith in the documented API). A cursor is an opaque pointer to the next page of results; when fetch returned one, you passed it back in as last to continue. update applied partial modifications to a single document without rewriting it.
The same operations existed as raw HTTP, which mattered when you wanted the endpoint directly rather than through the SDK. Cross-app data sharing, however, was not a platform feature — you built it yourself with your own token exchange.
What you didn't manage — and what that cost
| Concern | Collections' answer | Typical managed database |
|---|---|---|
| Provisioning | None; the store existed per app | Create an instance, network, credentials |
| Auth | Token injected at runtime | Keys and roles to rotate and scope |
| Tenancy | One user per dataset, enforced by the platform | Tenant columns or schemas; filter every query |
| Consistency | Strongly consistent within one collection | Usually consistent, sometimes configurable |
| Transactions | Single document, single collection only | Cross-table transactions |
| Secondary indexes | None; fetch scans items | Index design is your job |
| Scale target | Personal workloads: thousands of items, low request rates | General purpose |
Where the design hit limits
- Document size. Documented limits of the era were 1 MB per item and 10 MB per fetch response (check the archived docs if you need exact figures). Large payloads needed a different home.
- No secondary indexes. Every
fetchfilter scanned the collection's items. That was fine at hundreds of items and degraded noticeably somewhere past roughly ten thousand. Only primary-key lookups viagetstayed fast, because the key was the index. - No cross-collection transactions. A write spanning two collections could not be atomic. The design pushed you toward denormalizing — putting fields that must change together into one document.
- A narrow scale envelope. This was storage for one user's copy of an app, not a shared multi-user backend. Assuming otherwise was the root of most disappointments.
Mistakes the design invited
- Treating it like a shared production database. The per-user model meant "how many users do I have" never touched the data layer — until you built a feature where users see each other's data. Then you had to construct sharing and sync yourself.
- Trusting the local mock. The
space devmock did not enforce production limits the same way; item-size enforcement was relaxed locally, so a payload that worked on your machine could fail on deploy. Validate against documented limits, not local behavior. - Skipping an export story. The shutdown proved the point: there was no migration path after the deadline, so data lived or died with the platform. Any zero-config store you adopt should answer "how do I get everything out?" on day one.
- Shipping new code against the old SDKs. The
detapackages for Python, Node, and Go are unmaintained and receive no security updates. Treat them as dead dependencies to replace, not configure.
Checking anything that remains
If you own old Space code, it cannot run against the live service; plan a replacement rather than a revival. To confirm historical details — endpoint shape, limits, the shutdown timeline and export instructions — the primary records are the archived docs at docs.deta.space (via the Wayback Machine), the final space CLI release notes on GitHub, and the SDK source repositories such as deta-py. Verify exact figures there before quoting them.
If you are rebuilding the pattern on a live platform, test three things before committing: that credentials arrive via the environment and never appear in code, that one user's data is unreachable from another's by construction rather than by query discipline, and that a full export — every item, every collection — completes unattended. Those were the three properties that made Collections pleasant to use, and the third is the one its shutdown exposed as non-negotiable.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.