PocketBase SDK: three exports, a committed dist, and a Deno storage warning
PocketBase JavaScript SDK
At a glance
- What is it?
- The PocketBase JavaScript SDK is small enough to read in one sitting and its documentation is unusually candid about where it does not help you. It ships four builds behind an export map with no types condition and no engines field, depends on a global EventSource that Node does not have, dismisses filter injection as a client-side non-issue, and warns that Deno's LocalStorage is shared by every client hitting your server.
- Who is it for?
- This SDK suits a browser or React Native client talking to a self-hosted PocketBase, and its caveats section is the part to read before you wire it to anything. Three things to check.
- Can I use it commercially?
- Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
- Is it still maintained?
- No. The owners have archived the repository on GitHub, so it is read-only and no longer receives changes.
- What is it written in?
- Mainly TypeScript, according to GitHub's language statistics.
Answers come from the project's GitHub data, last synced on November 13, 2026, and from our analysis. They are not legal advice.
Star history
- pocketbase/js-sdk2,940 ★ · -1
| Repository | 2026-09-24 | 2026-11-13 |
|---|---|---|
| pocketbase/js-sdk | 2.9k | 2.9k |
-1 Stars in the last 50 days, measured from the daily GitHub snapshot. Compare with other projects
Editorial analysis
The npm package is named pocketbase, not js-sdk
The repository is js-sdk and the package you install is not. The manifest name is pocketbase, with keywords covering both spellings, so the import is from 'pocketbase' and the documentation's install command is npm install pocketbase --save. CommonJS consumers are pointed at a subpath instead of the root:
// Using ES modules (default)
import PocketBase from 'pocketbase'
// OR if you are using CommonJS modules
const PocketBase = require('pocketbase/cjs')For the browser the page offers two script tags, one loading /path/to/dist/pocketbase.umd.js as a classic script and one loading /path/to/dist/pocketbase.es.mjs as a module, in both cases instantiating with a base URL. So the same package serves three loading styles, and the file name in the script tag tells you which build you are getting: umd for a global, es.mjs for a module, cjs for a server require.
Three export entries, one odd extension, and no types condition
The exports field has three keys. The root entry resolves to ./dist/pocketbase.es.mjs, ./cjs to ./dist/pocketbase.cjs.js and ./umd to ./dist/pocketbase.umd.js. Two more fields sit outside it: main and module both point at the same ESM file, and types points at ./dist/pocketbase.es.d.mts. Two things follow from that arrangement. There is no types condition inside the export map and no declaration beside the ./cjs entry, so a TypeScript project consuming the CommonJS subpath has no types attached to that path through the map. And the react-native field resolves to ./dist/pocketbase.es.js, a plain .js extension where every other entry uses .mjs, which is a different artifact rather than an alias. The manifest also declares no engines field, so the Node version boundary discussed on the page is documentation only.
Realtime subscriptions depend on a global the SDK never installs
The realtime API is the part with a runtime prerequisite, and the page states it as a fact about Node rather than about the SDK. Node has no native EventSource implementation, so to use the realtime subscriptions you load a polyfill and assign it to the global yourself:
// for server: npm install eventsource --save
import { EventSource } from "eventsource";
global.EventSource = EventSource;React Native is handled with a different package, react-native-sse, assigned to the same global in the same way. Because the assignment is what makes it work, the polyfill has to be in place before the first subscription is created, and a server that initialises the SDK during module load will pick up whichever value global.EventSource holds at that moment. Node below 17 has a second prerequisite, a fetch polyfill, with cross-fetch recommended and its polyfill entry imported once.
The filter helper exists because client-side injection was ruled out
There is a helper for building filter strings, pb.filter(expr, params), which takes placeholder parameters written as {:paramName} and fills them from an object. Its recommendation is qualified in a way worth reading closely: the page recommends it for server-side list queries in Node, Deno or Bun that accept untrusted user input as a filter string, because it escapes the generated expression, and then adds that on the client-side this is not much of an issue. So the project draws the security boundary at the server, not at the string. The supported parameter values are listed explicitly, and the list is where the sharp edges sit: undefined is stringified as null, a Date is stringified into the format PocketBase expects, and everything else goes through JSON.stringify, which means an object parameter produces JSON in a filter expression rather than an error.
The default auth store falls back to memory, and Deno shares its storage
Authentication state lives on pb.authStore, and the default implementation, LocalAuthStore, uses the browser's LocalStorage when it is available and otherwise falls back to runtime memory, which the page spells out as meaning you authenticate again after a page refresh or a service restart. It also syncs state between multiple tabs on its own. The warning attached to it is the sharpest thing in the documentation: Deno also supports LocalStorage, but unlike a browser, where the client is the only user, Deno's LocalStorage is shared by all clients making requests to your server by default. So the same default that is convenient in a tab is a shared-session store in a Deno process, and the page gives AsyncAuthStore as the way out, a helper for integrating any third-party async storage, which is the usual requirement when working with React Native.
dist is in the repository and the build deletes it first
The top level of the repository includes a dist directory, which means the build output is version controlled alongside the source, and .npmignore sits next to it. The build script makes the relationship explicit: it removes dist and then runs rollup, in one line, with the watch variant being the same command with a watch flag. There is no separate clean step, so the removal is part of the build rather than a target of its own, and a publisher runs the build through prepublishOnly. A small consequence of a committed dist is that the tree can contain an artifact that no longer matches src, and nothing in the repository marks which is which. The rest of the root is conventional: rollup.config.mjs and vite.config.ts for bundling and tests, tsconfig.json, CHANGELOG.md, LICENSE.md and the src and tests directories.
Prettier is pinned to an exact version while everything else uses a caret
The devDependencies read like a normal set with one exception. prettier is pinned to 3.2.4 with no range, while rollup sits at ^4.0.0, typescript at ^5.1.6, vitest at ^2.0.0 and the two rollup plugins carry their own carets. Pinning a formatter exactly is a defensible choice, since a formatting change is a diff across the whole repository, and it is the only tool here that does it. The formatting configuration is inline in the manifest rather than in a separate file, with tabWidth 4, printWidth 90 and bracketSameLine true. The format script is worth noting too: it invokes prettier through npx against src and tests with a write flag, so running it resolves the binary through npx rather than through the local node_modules path.
The caveats list names ten topics and the text carries four
The contents list is a map of what the documentation claims to cover: binding filter parameters, file upload, error handling, the auth store with its default, async and custom variants plus the common fields and methods, auto cancellation, specifying TypeScript definitions, custom request options, send hooks, SSR integration and security. The text on the page runs through the filter helper, file upload, error handling and the auth store, and stops inside the AsyncAuthStore paragraph. So the last six topics, including auto cancellation, the send hooks, the SSR integration and the security section, are named but not written here, and a reader looking for how cancellation behaves or where hooks fire has to go to the documentation site. What is present is honest about its own edges, with error handling normalised into a ClientResponseError carrying url, status, response, isAbort and originalError.
Editorial conclusion
This SDK suits a browser or React Native client talking to a self-hosted PocketBase, and its caveats section is the part to read before you wire it to anything. Three things to check. Where your auth state lands, because the default store uses LocalStorage, falls back to memory when that is unavailable, and the page warns specifically that a Deno server's LocalStorage is shared by all clients, so a server-side use of the default store leaks one session into another. Whether you build filters on the server from untrusted input with pb.filter rather than by concatenation, since that is the one place the page says injection is a real risk. And which build your bundler resolves, because the export map has three entries and no types condition, and the react-native condition resolves to a differently named file from the main one, so TypeScript users of the CommonJS subpath get no types from the map.
Frequently asked questions
How do I install the PocketBase JavaScript SDK?
In a browser, load /path/to/dist/pocketbase.umd.js with a classic script tag or /path/to/dist/pocketbase.es.mjs as a module. In Node, run npm install pocketbase --save, then import PocketBase from 'pocketbase' for ES modules or require('pocketbase/cjs') for CommonJS. The package is named pocketbase even though the repository is js-sdk.
Why do realtime subscriptions not work with the pocketbase SDK in Node?
Because Node has no native EventSource, and the page instructs you to load a polyfill and assign it yourself: for a server npm install eventsource --save and import { EventSource } from "eventsource", for React Native npm install react-native-sse --save, then set global.EventSource = EventSource. Node below 17 also needs a fetch polyfill such as cross-fetch.
How does pb.filter protect a PocketBase query from injection?
pb.filter(expr, params) builds a filter string from {:paramName} placeholders filled from an object, and the page recommends it for server-side list queries that take untrusted input, because it escapes the generated expression. It says the client-side case is not much of an issue. Parameters accept string, number, boolean, null, undefined stringified as null, Date, and anything else through JSON.stringify.
Where does the PocketBase SDK keep the auth token?
On pb.authStore. The default LocalAuthStore uses browser LocalStorage when available and otherwise falls back to runtime memory, so a refresh or service restart means authenticating again, and it syncs state across tabs. The page warns that Deno's LocalStorage is shared by all clients making requests to your server, and offers AsyncAuthStore for third-party async storage such as React Native.
Official sources
Add this badge to your README
If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.
[](https://hysenlabs.com/projects/pocketbase-js-sdk)Star history chart for your README
[](https://hysenlabs.com/projects/pocketbase-js-sdk)Was this useful?