PocketBun is a Bun-native reimplementation/port of PocketBase with the goal of being API-compatible (drop-in for clients + Admin UI).
- Runtime target: Bun only (no Node.js support needed).
- Language: TypeScript (keep types practical; avoid type wizardry).
- Serve the existing PocketBase Admin UI unchanged as static assets.
PocketBun is a separate open-source project and must clearly credit PocketBase and preserve upstream license notices for any copied code/assets.
- Repo: https://github.com/pekeler/pocketbun
- GitHub issues/comments/PR comments: use literal multiline strings or
-F - <<'EOF'(or $'...') for real newlines; never embed "\n".
When implementing behavior, prioritize matching PocketBase observable behavior:
- routes, status codes, JSON response shapes
- query params semantics (filters, sort, expand, fields, paging)
- auth/token behavior
- realtime protocol behavior (SSE)
- error formatting
- JS developer-facing APIs (method names/casing and usage patterns) as documented by PocketBase JSVM
If you’re unsure about an edge case:
- check PocketBase docs
- check PocketBase source in the pinned upstream checkout (see below)
- if still unclear, run upstream PocketBase at the pinned tag and compare behavior, then encode it in tests. If you discover a behavior difference that cannot be avoided, confirm with the repo owner that the incompatibility is acceptable before documenting it. Only then add it to the README’s “Known Differences” list (keep that list very short).
Goal: maximize long-term maintainability and upstream-syncability by keeping PocketBun structurally close to the PocketBase Go codebase, while preserving observable behavior.
- Behavior first: Match PocketBase observable behavior (API, status codes, JSON shapes, error formats, auth, realtime) even if the internal implementation differs.
- Mechanical translation preferred: Avoid “cleanup”, refactors, or re-architecture unless needed for correctness or Bun constraints.
- Traceability: When porting a file, add a short header comment linking to the upstream source path (no version/hash;
pocketbase_tag.txtis the source of truth), e.g.// Ported from pocketbase/<path>- If a single TS file merges multiple upstream files, list all upstream source paths in that header comment.
- Preserve upstream comments: Copy upstream comments (doc comments and relevant inline notes) into the TypeScript port. If comments are missing, backfill them so the TS file reflects upstream commentary.
- Comment backfill is mandatory: When you notice missing upstream comments in already ported code, add them immediately rather than deferring.
- Non-upstream files: Any source or test file without an upstream counterpart must include a short header comment explaining why the file exists (keep these files to a minimum).
- 1:1 file mapping (when reasonable): Prefer one
.tsfile per corresponding.gofile and mirror directory structure (Go packages → TS folders) to keep diffs and future syncing straightforward.- If strict 1:1 creates unnatural modules (circular imports, huge files, etc.), it’s OK to merge/split — but document it in a comment near the top of the file and list every upstream source file included.
- Naming: Prefer upstream naming and concepts for internal identifiers (types/functions), even if not idiomatic JS/TS, as long as it doesn’t reduce clarity or cause bugs.
- For public JS/TS APIs, prioritize PocketBase JSVM naming/casing over Go exported naming.
- Go-style names may exist as compatibility aliases, but must not be the only public API form when a JSVM equivalent exists.
- Docs/examples should use JSVM-style names and casing.
- Do not rename just for style.
- Only deviate when necessary: Deviations are allowed when required by JS/Bun semantics (async I/O, concurrency model, time/number handling, resource cleanup, etc.).
- When you deviate, leave a brief comment explaining why.
- Document significant differences: If a ported file intentionally diverges from upstream behavior/implementation, document the difference and the rationale in the file.
- Compatibility shims: Prefer small internal helpers (
src/internal/compat/*) to model Go-like primitives (errors/time/sync/http/sql) so most files remain a straightforward, 1:1 port. - Dependencies: Prefer Bun built-ins first (e.g.
Bun.serve,bun:sqlite, Web APIs).- If PocketBase uses a Go third-party library and Bun lacks equivalent functionality, choose an npm dependency that is:
- actively maintained,
- reasonably popular,
- small/specific (avoid heavy frameworks),
- permissively licensed (MIT/Apache/BSD).
- Avoid adding dependencies for trivial utilities—write small local helpers instead.
- If PocketBase uses a Go third-party library and Bun lacks equivalent functionality, choose an npm dependency that is:
- Regression tests required: For each ported subsystem or endpoint, add/adjust tests so behavior is pinned. If you discover an upstream edge case, add a regression test immediately.
- For public API naming/alias compatibility (especially JSVM-facing names), add regression tests that lock expected method names and behavior.
When writing complex features or significant refactors, use an ExecPlan (as described in .agents/PLANS.md) from design to implementation.
Keep the active plan in .agents/EXECPLAN.md.
- Follow
.agents/PERFORMANCE.mdfor all performance work. - Optimize only measured hotspots and keep PocketBase behavior compatibility.
- Prefer low-allocation hot-path code: combine multi-pass array chains, move invariants out of loops, and use
Set/Mapfor repeated membership checks. - Keep changes small and verify with full checks:
bun run format:fixbun test --concurrentbun run typecheckbun run lint
Keep these defaults unless we intentionally document a deviation:
- REST API base path:
/api/ - Admin UI:
/_/ - If
pb_public/exists, serve it at/as static files. - Default app directories (relative to CWD unless configured):
pb_data/(SQLite DB + storage, should be gitignored)pb_migrations/(schema/data migrations, safe to commit)pb_hooks/(server-side hooks/extensions)
We track a specific PocketBase release tag in a simple text file:
pocketbase_tag.txt— contains a tag likev0.36.1.upstream/pocketbase— local git checkout used for reference (NEVER commit)
Use the repo script (must remain simple: clone + checkout):
bun run upstream:sync
Conventions:
.upstream/is local only and must be in.gitignore.- Never edit upstream files; treat it as read-only reference material.
- Bumping upstream:
- edit
pocketbase_tag.txt - run
bun run upstream:sync - update PocketBun code + tests until compatibility is restored
- edit
We reuse PocketBase Admin UI as static assets (do not modify upstream UI code manually).
Conventions:
- Keep vendored UI under
vendor/pocketbase-admin-ui/dist. - Update the vendored UI only by copying from:
.upstream/pocketbase/ui/distafter syncing to a specific tag. - Keep a copy of PocketBase’s MIT license text next to the vendored UI assets.
- Use ESM (
import/export), no CJS compatibility work required. - Prefer Bun/Web APIs:
Bun.servefor HTTPbun:sqlitefor SQLite- Web
Request/Response,URL,fetch, streams
- Avoid Node-only dependencies and Node-specific runtime assumptions.
node:builtins are OK only if Bun supports them and they keep things simpler.
- Tooling/scripts must use Bun runtime entrypoints (
bun .../bun run ...) instead ofnode ....- Prefer
.jsscript files for Bun tooling helpers unless there is a specific reason to use another extension.
- Prefer
- Keep dependencies minimal; prefer small utilities over frameworks.
- Keep types useful but lightweight:
- explicit types for exported/public APIs
- internal code can rely on inference
- avoid complex generics and type-level tricks
- Prefer runtime validation + clear errors at boundaries over “perfect types”.
- Don’t “TypeScript-ify” everything; correctness + clarity > cleverness.
PocketBun should make server-side extensibility practical in Bun:
- Support loading code from
pb_hooks/(and allow TS/ESM naturally). - Keep the ergonomics simple and documented.
- Where possible, keep naming/semantics close to PocketBase hook concepts so existing users can migrate.
Use bun test --concurrent and keep tests focused on compatibility. Always run all four — bun run format:fix, bun test --concurrent, bun run typecheck, and bun run lint — before committing any changes to src/ or tests/, and fix any errors or warnings they report. Treat compiler/runtime warnings (including deprecation warnings surfaced during tests or bun run start) as issues to fix before committing. Use bun run format for a formatting check when you don’t want to apply auto-fixes. When rerunning tests during fixes, prefer bun test --only-failures --concurrent to keep output quieter, and expect ~40s of no output before results (set a longer timeout so it doesn’t look hung).
When running with --concurrent, mark tests that mutate global state or shared resources as serial using test.serial / it.serial (examples: process.env, process.argv, globalThis, shared temp paths, or other global singletons). Keep those serial exceptions minimal; everything else should run concurrently.
When porting any upstream code (especially validators/fields), also port the corresponding upstream tests and use them as the primary verification of correctness.
Keep tests in the same relative paths as upstream: if the upstream test lives next to the code (for example core/validators/db_test.go), place the ported test next to the TS source (for example src/core/validators/db.test.ts). Only keep tests under tests/ when the upstream file is under tests/, or when the test is PocketBun-only (and then include the required “why this file exists” header comment).
Minimum expectations:
- tests for core API endpoints (status + JSON shape)
- tests for auth flows we implement
- tests for realtime basics (connect/subscribe/events)
- regression tests for any compatibility edge case discovered
Add tests whenever:
- you implement an endpoint
- you fix a bug
- you observe a behavior difference vs upstream
bun run upstream:sync— sync.upstream/pocketbaseto the tag inpocketbase_tag.txtbun test— run test suitebun run typecheck— run TypeScript typecheck (no emit)bun run lint— run oxlint (type-aware)bun run format— check formatting with oxfmtbun run format:fix— apply formatting fixes with oxfmt- For Codex approval persistence, keep command prefixes/strings stable across runs.
- If params/env need to change repeatedly, prefer editing a dedicated wrapper script (for example under
scripts/) and rerun the same wrapper command instead of issuing new inline env-var command variants each time.
- Make small, focused commits.
- Do not reformat unrelated files.
- If you copy upstream code/assets, preserve required license notices and document what was copied.
- If PocketBun intentionally deviates from PocketBase behavior, document it explicitly (README / docs).
- For non-trivial code changes, do a quick performance review of affected paths; if there is potentially measurable overhead/regression, apply a simple optimization before merge (or document why not).
- Keep
/CHANGELOG.mdup to date: every important user-facing change must be recorded under an appropriate version (orUnreleased) before release. - Keep changelog entries concise and outcome-focused; skip internal-only details (tests, refactors, CI/tooling churn) unless they directly affect users.
- Write changelog entries from the user's point of view: lead with what is new, better, or fixed for users, not with implementation details or the internal work log.
- Treat the changelog as lightly promotional as well as informative: it should help existing and potential users feel PocketBun is making meaningful progress, without turning into hype or marketing fluff.
- Use consistent wording for compatibility bumps, for example:
Now compatible with PocketBase vX.Y.Zplus a link to the upstream changelog. - If a change is directly ported from upstream, list it as an indented sub-point under the compatibility bullet instead of as a separate top-level PocketBun item.
- Commit gate: do not create a commit with user-facing or developer-relevant changes unless
/CHANGELOG.mdhas a concise entry under the target version (orUnreleased) for those changes. - Do not leave
TBDin the active changelog section when committing user-facing or developer-relevant changes.
PocketBun versions must be valid SemVer for npm/Bun tooling, and must clearly encode which PocketBase release we target.
We mirror the upstream PocketBase X.Y.Z and add a PocketBun-specific patch as a SemVer pre-release:
PocketBun version = X.Y.Z-pocketbun.N
X.Y.Zis the PocketBase version we are compatible with.Nis a PocketBun-only increment for fixes/changes that keep compatibility with the same upstreamX.Y.Z.
Examples:
- Compatible with PocketBase
v0.36.1(first PocketBun release):0.36.1-pocketbun.0 - Bugfix release while still compatible with
v0.36.1:0.36.1-pocketbun.1 - After syncing to PocketBase
v0.36.2:0.36.2-pocketbun.0
pocketbase_tag.txtcontains the upstream tag, e.g.v0.36.1.- The
X.Y.Zinpackage.jsonmust matchpocketbase_tag.txt(strip the leadingv). - When
pocketbase_tag.txtchanges to a newvX.Y.Z, resetNto0.
- Never publish a plain
X.Y.Z(without the-pocketbun.Nsuffix). - Git tags/releases should match the package version, prefixed with
v, e.g.v0.36.1-pocketbun.2. - Release notes must state: “Compatible with PocketBase vX.Y.Z” and, when available, include the upstream commit hash.