Skip to content

client.kv

See the Storage guide for usage and patterns. For files, use client.storage.

set<T>(collection: string, id: string, value: T, opts?: { encrypt?: boolean }): Promise<void>

Stores value under collection/id. Encrypted at rest by default; pass { encrypt: false } to store plaintext. Needs an unlocked identity for encrypted writes.

get<T>(collection: string, id: string): Promise<T | null>

Returns the value (transparently decrypted) or null if absent.

delete(collection: string, id: string): Promise<boolean>

Removes the key; resolves to whether it existed.

list(collection: string): Promise<string[]>

Returns the ids in a collection (not values). The server filters by the collection and returns its keys a page at a time, which list follows to the end; a collection of up to 1,000 keys is one request. There is no server-side query on values — filter client-side after fetching (see Querying).

keys(prefix?: string): Promise<string[]>

Returns the full keys (todos/t1, not t1) in the signed-in user’s space that start with prefix, or every key when it is omitted. Values are not fetched. The space is shared by every app the user signs into, so expect keys you did not write: the VFS’s vfs/…, version control’s vcs/…, and the SDK’s own chat-keys and space-keys/….

on(event: "change", handler: (e: StorageChangeEvent) => void): () => void

Subscribes to the realtime change feed for the signed-in user (across devices). Returns an unsubscribe function. Requires a WebSocket global.

interface StorageChangeEvent<T = unknown> {
collection: string;
id: string;
type: "set" | "delete";
data: T | null; // decrypted value on "set"; null on "delete"
}

Reserved. Server-side query is intentionally unsupported (encrypted-at-rest); calling it throws. Use list() + client-side filtering.

In a browser, client.kv is offline-aware (see client.offline). set and delete write the local cache first. If the server can’t be reached the write is queued and replayed on reconnect (delete then resolves true optimistically). A response from the server — including a refusal — is never queued: it throws. get falls back to the cache only when the server is unreachable, and keys / list merge the server’s keys with un-synced local ones.

Message containsMeaning
not signed inNo active session — sign in first.
identityEncrypted op needs an unlocked identity — login() or unlock().
HttpErrorThe server refused the request; carries status and body.
PaymentRequiredErrorA set was refused for payment (paid-only app on a Free user, or a past-due payment) — see Errors. delete still works.
no \WebSocket` global`on() needs a WebSocket (browsers; Node 22+ or a shim).