Push app content with per-user partitions
Index content behind a login, one partition per user, the way Toread indexes each reader's library.
This guide walks through the pattern Toread uses for its reading library: an index where every record belongs to exactly one user, and a user only ever searches their own records.
1. Create the index with a partition key
PUT /1/indexes/library/settings
{ "indexTime": { "partitionKey": "userId" } }Set this once, before you write any records — changing partitionKey on an index that already has data is not supported; create a new index instead.
2. Push records with the partition attribute
Every record needs the attribute named by partitionKey:
POST /1/indexes/library/records
{
"operations": [
{
"action": "upsert",
"record": {
"objectID": "item-482",
"userId": "user_9f2",
"title": "The gentle art of note-taking",
"url": "https://example.com/read/item-482",
"version": 1732550400000
}
}
]
}Push on the events that change what should be searchable: an item becomes ready, its content updates, it is archived or deleted, or a highlight or note is added or changed. Use your own updatedAt as version — see Versions and last-writer-wins.
3. Mint a scoped token per reader
Your server mints a token pinned to that one partition, using your write (or a dedicated signing) key — never expose that key to the browser:
import type { ScopedTokenClaims } from '@suo/protocol';
const claims: ScopedTokenClaims = {
keyId: process.env.SUO_KEY_ID,
indexes: ['library'],
partition: `user_${session.userId}`,
filters: [],
exp: Math.floor(Date.now() / 1000) + 300,
ratePerMinute: null,
};Mint a fresh token per session or page load with a short expiry — see Scoped tokens for the full claims shape, how signing works today, and how expiry is enforced.
4. Search with the token, not a shared key
<suo-search host="https://api.usesuo.com" scoped-token="{{token}}" index-name="library"></suo-search>Because the token pins the partition, there is no filters parameter for the client to get wrong — a bug in your front end cannot leak another reader's records.
5. Handle account deletion
Clear the whole partition in one call when an account is deleted:
DELETE /1/indexes/library/partitions/user_9f2See Clear a partition — this removes every record and tombstone in that partition, with no undo.
Backfilling existing users
For a partitioned index created after your app already has users, push a full backfill batch per user once, then switch to incremental pushes on the events above going forward.