PocketBun Differences From PocketBase

This page tracks user-relevant differences between PocketBase and PocketBun.

Notation used in examples:

Quick links:

PocketBase To PocketBun Migration Checklist

Use this as a quick migration recipe for an existing PocketBase project.

  1. Switch executable and update flow.
    • Replace pocketbase commands with pocketbun.
    • There is no PocketBase-style binary self-update command; update via package manager.
  2. Create (or convert to) a Bun project.
    • Option A: convert your existing project directory:
      • initialize package metadata: bun init
      • add PocketBun dependency: bun add pocketbun
    • Option B: scaffold a new PocketBun project and copy your existing data/code:
      • create project: bun create pocketbun my-app
      • copy your PocketBase pb_* directories into the new project (pb_data, pb_hooks, pb_migrations, optional pb_public)
  3. Keep the project layout, but verify startup working directory.
    • Keep pb_data, pb_hooks, pb_migrations, and optional pb_public.
    • PocketBun resolves default paths from current working directory (CWD), so start from your project root or pass explicit dirs.
  4. Move hooks as-is, then fix hook-chain calls.
    • In handlers that call e.next(), return/await it:
      • sync: return e.next()
      • async: const err = await e.next(); if (err) return err; ...
    • This is especially important for onBootstrap when using async startup paths.
    • If you see OnBootstrap hook didn't fail but the app is still not bootstrapped, this is usually the cause.
    • In .pb.ts hooks, use standard import for neighboring files and dependencies when needed.
  5. Keep API clients and route assumptions.
    • Existing client SDK usage should continue to work with the same API base paths (/api/, /_/).
  6. If you embed PocketBun programmatically, prefer server-side JavaScript package API names.
    • Prefer registerServerJS* / mustRegisterServerJS* and ServerJSConfig.
    • RegisterJSVM* / MustRegisterJSVM* and JSVMConfig remain aliases because PocketBase’s upstream JavaScript extension package is named jsvm.
    • RegisterHooksPlugin* / MustRegisterHooksPlugin* and RegisterServerJS* / MustRegisterServerJS* remain deprecated aliases for compatibility with released PocketBun versions.
  7. Run a migration smoke test before deploying.
    • Start: pocketbun serve --dev
    • Verify health: GET /api/health
    • Verify custom hooks/routes and auth flows you use in production.
    • If you have older generated collection/schema migrations, update them to use app.forMigrations() as described below.
  8. If you used PocketBase’s automatic HTTPS mode, put PocketBun behind a reverse proxy.
    • Run PocketBun on HTTP, for example pocketbun serve --http 127.0.0.1:8090.
    • Terminate HTTPS in Caddy, NGINX, Traefik, a load balancer, or another reverse proxy.
  9. Review the sections below for details.
    • Use this checklist for the quick pass, then check each section in this page only where your app uses that feature.

Runtime And Distribution

PocketBase:

PocketBun:

CLI Defaults And Paths

PocketBase defaults are resolved relative to executable location. PocketBun resolves defaults from current working directory.

Default paths in PocketBun CLI:

This prevents accidental writes into node_modules-adjacent paths when used as package dependency.

Server-Side JavaScript Plugin Naming

PocketBase’s upstream JavaScript extension package uses jsvm naming because it runs code in an embedded JavaScript VM. PocketBun runs hooks and JavaScript migrations with Bun, so PocketBun’s preferred package API names use ServerJS:

All names map to the same server-side JavaScript registration behavior.

Hooks API And Module Loading

PocketBun supports PocketBase-style lowercase server-side JavaScript naming and keeps Go-style aliases where applicable. The uppercase aliases exist only for older PocketBun hooks and migrations, and are deprecated compatibility aliases; new pb_hooks and pb_migrations code should use the lowercase names used by PocketBase JavaScript docs and pb_data/types.d.ts.

To update older PocketBun hooks and migrations automatically, run:

pocketbun server-js upgrade-source

The command scans ./pb_hooks and ./pb_migrations by default. Use pocketbun server-js upgrade-source --check in CI to fail when deprecated aliases remain, or pass explicit files/directories to limit the rewrite. The fixer rewrites deprecated JavaScript/TypeScript member access and known PocketBun option-object keys such as e.Record.GetString(...), $app.OnServe(), app.RunInTransaction(...), app.CreateBackup(...), app.RecordQuery(...), app.Restart(), record.GetDateTime(...), dateTime.Before(...), requestInfo.HasSuperuserAuth(), cookie.Valid(), command.SetOut(...), message.WriteSSE(...), ctx.Deadline(), form.Validate(), apiErr.RawData(), validationErr.SetMessage(...), { Func, Id, Priority }, new TextField({ Name: "title", Help: "shown" }), and new Command({ Use: "serve", RunE: fn }); updates released package aliases such as RegisterHooksPlugin*, RegisterJSVM*, RegisterServerJS*, RequireGuestOnly, Static, TemplateLangJS, and PascalCase package config keys; and updates old generated collection migrations to use app.forMigrations(). It does not rewrite comments, strings, unrelated application data keys such as HTTP headers or globalThis.secrets.*, or class constructor identifiers such as new ApiError(...), new ValidationError(...), or new RecordUpsertForm(...). Run it with a clean working tree and review the diff before committing.

For pb_hooks module loading:

For code-first BaseApp usage:

Migration Hook Behavior

PocketBase generated collection migrations save collections through the normal app save path. That means custom model/collection hooks can run when old migrations are replayed on a fresh database.

PocketBase does not acknowledge this as a bug; the upstream position is that model save hooks and validations are intentionally part of save. PocketBun disagrees for generated schema migrations because historical migrations should not depend on current application/business hooks. This is the same class of replay hazard described by Rails in Using Models in Your Migrations.

PocketBun generated JS collection migrations use app.forMigrations() instead. The returned app view skips user hooks registered after app construction while preserving PocketBun system hooks required for collection persistence, table sync, cache reloads, and view updates.

For older generated collection/schema migrations, update the migration to use a migration app view:

migrate((app) => {
  const migrationApp = app.forMigrations()

  const collection = migrationApp.findCollectionByNameOrId("posts")
  collection.fields.add(new TextField({
    name: "slug",
    required: false,
  }))

  return migrationApp.save(collection)
}, (app) => {
  const migrationApp = app.forMigrations()

  const collection = migrationApp.findCollectionByNameOrId("posts")
  collection.fields.removeByName("slug")

  return migrationApp.save(collection)
})

For generated collection snapshots, use:

return app.forMigrations().importCollections(snapshot, false)

Migration rule: migrations must be able to run years later with the current app code.

Async API Extensions

PocketBun keeps sync-compatible APIs but adds async alternatives for I/O-heavy paths.

Area PocketBase-compatible sync API PocketBun async extension
Archive helpers create, extract createAsync, extractAsync
App bootstrap/serve app.bootstrap(), serve(...) app.bootstrapAsync(), serveAsync(...)
Migration helper migrate(...) migrateAsync(...)
Server-side JavaScript register registerServerJS(...) registerServerJSAsync(...)
Filesystem factories newFilesystem() newFilesystemAsync()
Server-side JavaScript helpers $http.send(...), $os.readFile(...) $http.sendAsync(...), $os.readFileAsync(...)

Operational Differences

HTTPS

PocketBase can run a public HTTPS server directly with automatic Let’s Encrypt certificates:

PocketBun does not include PocketBase’s built-in automatic HTTPS/Let’s Encrypt server mode. The equivalent PocketBun deployment pattern is to run PocketBun over HTTP and terminate HTTPS in a reverse proxy such as Caddy, NGINX, Traefik, a platform load balancer, or a CDN edge.

Recommended PocketBun backend command:

pocketbun serve --http 127.0.0.1:8090

Minimal Caddy example:

example.com {
  reverse_proxy 127.0.0.1:8090
}

The pocketbun serve domain arguments, the --https flag, and programmatic ServeConfig.httpsAddr / ServeConfig.certificateDomains settings are intentionally unsupported and return an explanatory error instead of starting a server.

Activity logs

PocketBun persists activity logs through a background worker to reduce main-thread blocking.

Cron Scheduling

PocketBun app cron scheduling uses Bun’s native Bun.cron(...) scheduler and interprets cron expressions in UTC.

Thumbnails

PocketBun uses Bun’s built-in Bun.Image for image resizing. Output bytes may differ from the PocketBase Go image stack.

Templates

PocketBun $template helper supports common PocketBase template patterns.

For closer Go text/template parity, install optional go-text-template.

Server-Side JavaScript $filepath

PocketBun exposes the same $filepath method names as PocketBase, but it does not fully match Go path/filepath edge cases.

Server-Side JavaScript RequestEvent Request/Response Surface

For custom routes, e below means the route event parameter passed to routerAdd(..., (e) => { ... }).

PocketBun supports the common PocketBase custom-route access patterns:

Incompatibilities in this area:

SQL placeholders and dbx rewriting

PocketBun supports dbx-style query marker rewriting for SQLite helpers. Logged placeholder formats can differ while query behavior is compatible.

Dev SQL logging format

In --dev mode, PocketBun prints SQL logs using a Bun-native format based on the executed rewritten SQL ([X.XXms] <sql>). The exact formatting may differ from PocketBase and is informational only.

Windows behavior

PocketBase Docs Topics That Do Not Apply Directly

These upstream topics are either intentionally excluded or need reinterpretation for PocketBun:

These are not bugs in PocketBun docs; they are product-level differences.

Intentional Omissions

Intentionally not provided in PocketBun:

Deferred until demand: