Connect with the SDK
voidbase answers PocketBase's API, so the client is PocketBase's JavaScript SDK. voidbase publishes a fork of it, @voidbase-cloud/sdk, kept in step with upstream, that adds typed collections, a plugin surface and the plugins that ride on it. The upstream pocketbase package works unchanged too.
Install it
bun add @voidbase-cloud/sdk # or: npm install @voidbase-cloud/sdkimport PocketBase from "@voidbase-cloud/sdk" is a drop-in for the upstream import: the default export is the same Client class, and every named export of the upstream package is exported under the same name.
Use it
The only thing you supply is the address of your instance. Everything after that is the SDK's own API, and works exactly as its documentation says.
import PocketBase from "@voidbase-cloud/sdk";
const pb = new PocketBase("https://your-instance.example.com");
// sign in; the token is kept in pb.authStore and sent with every request after this
await pb.collection("users").authWithPassword("you@example.com", "your-password");
// read, with filtering, sorting and related records in one call
const posts = await pb.collection("posts").getList(1, 20, {
filter: "published = true && author.verified = true",
sort: "-created",
expand: "author",
});
// write
await pb.collection("posts").create({ title: "Hello", body: "...", author: pb.authStore.record?.id });
// and follow changes as they happen, over one connection
pb.collection("posts").subscribe("*", (e) => console.log(e.action, e.record));The auth token lives in pb.authStore, so signing in once covers the rest of the session, and in a browser it is persisted for you. File URLs come from pb.files.getURL(record, filename), with a thumb option for a resized version.
One SDK instance for the whole application is the intended pattern on the client side. On a server, make one per request instead, so one user's token never leaks into another user's call.
Type it from your instance
voidbase types reads the instance's own API description, /api/openapi.json fetched as a superuser, and writes one file: an interface per collection (PostsRecord, UsersRecord, with select literals, typed expand for relations and the system fields), a Collections map, and a TypedPocketBase type. VoidBase takes the map and types collection(name) from it, so a renamed field is a compile error at the call rather than an empty value at runtime. The file imports nothing, so it compiles against whichever SDK the project installed. There is no watch mode: run it again when the collections change, or put it in the build.--watch keeps it in step while you work: it polls the instance and rewrites the file only when the generated types actually change, naming what moved.
voidbase types --url https://your-instance.example.com --token <superuser token>
# or --email and --password; writes src/voidbase.ts, --out for another path, --json <file> for a saved documentimport { VoidBase } from "@voidbase-cloud/sdk";
import type { Collections } from "./voidbase";
const pb = new VoidBase<Collections>("https://your-instance.example.com");
const post = await pb.collection("posts").getOne("RECORD_ID"); // PostsRecord
post.title; // string
post.titel; // compile error
post.expand?.author; // UsersRecord | undefined
pb.collection("_superusers"); // not in the map: untyped, as the plain client// with the upstream pocketbase package instead
import PocketBase from "pocketbase";
import type { TypedPocketBase } from "./voidbase";
const pb = new PocketBase("https://your-instance.example.com") as TypedPocketBase;Plugins
pb.use(plugin) installs a plugin once per name and returns the client; pb.unuse(name) removes it. A plugin is an object with a name and an install function, and what it sits in is the hook lists on the request path of every call: pb.hooks.beforeSend, afterSend and onError. It goes on a Client or a VoidBase alike.
import PocketBase, { type Plugin } from "@voidbase-cloud/sdk";
const tracing: Plugin = {
name: "tracing",
install(client) {
return client.hooks.beforeSend.add((url, options) => {
options.headers = Object.assign({}, options.headers, { "X-Trace": crypto.randomUUID() });
}); // what install returns is the uninstall, for client.unuse("tracing")
},
};
const pb = new PocketBase("https://your-instance.example.com").use(tracing);Six ship with the SDK, each from its own entry point so an application that does not want one does not carry it:
| Entry point | What it adds |
|---|---|
@voidbase-cloud/sdk/offline | offline(): writes that survive a tunnel. A create, update or delete the network refused is queued, answered optimistically, persisted, and replayed in order when the connection is back. |
@voidbase-cloud/sdk/pwa | registerServiceWorker(), installPrompt() and the pwa() plugin, the page side of the service worker a stack app's pwa option serves: the update prompt, the skip-waiting handshake, and an unregister that clears the caches. |
@voidbase-cloud/sdk/editable | editable(): an element marked data-vb-edit="collection:id:field" becomes editable for a signed-in user and saves through the same update call and rules as any write; data-vb-edit-markdown edits the markdown source with a small toolbar. |
@voidbase-cloud/sdk/ai | ai(): pb.ai.chat() over the instance's Workers AI route, with the instance's tools when the token allows them, and pb.ai.conversations for chats kept as records on the server, streamed and followed over realtime. |
@voidbase-cloud/sdk/payments | payments(): checkout, portal and cancel through whichever provider the instance loaded (Stripe, Polar or Lemon Squeezy), and mine() for the customer, subscriptions and payments the webhooks wrote for the signed-in user. |
@voidbase-cloud/sdk/seo | seo(): a page's tags from the instance, written into the head on every navigation, and seoHead() for a server-rendered page. |
The offline queue is the one most applications reach for first:
import PocketBase from "@voidbase-cloud/sdk";
import { offline } from "@voidbase-cloud/sdk/offline";
const pb = new PocketBase("https://your-instance.example.com").use(offline());
// with the network gone, this answers at once; the replay creates this record, with this id
const post = await pb.collection("notes").create({ text: "written on the train" });
pb.offline.pending(); // what is still waiting
await pb.offline.flush(); // or wait for the window "online" eventWhere to read on
All of this applies to voidbase without translation:
- @voidbase-cloud/sdk the fork: every option of every plugin above, and the upstream SDK's own documentation below it
- The JavaScript SDK every method, and the SDKs for other languages, which speak the same API
- Records API what each endpoint accepts and returns
- API rules and filters the query and permission language
- Authentication password, OAuth2, one-time codes and multi-factor sign-in
- Files uploads, protected files and thumbnails
Your own instance describes itself too: /api/docs is its API reference, scoped to the token you open it with, and /api/mcp is the same thing for an agent.
No instance to point at yet? Run one in about a minute.
Fix it yourself. The link below opens this file in GitHub's editor and forks the repository for you if you need one, and your change becomes a pull request without leaving the browser.