Migrating from PocketBase
slonbase implements PocketBase's API (0.23 and later), so most apps move over by pointing the SDK
at the new URL. slonbase import-pocketbase brings over the data.
#Importing the data
slonbase setup --database-url 'postgres://...' --email you@example.com # once
slonbase import-pocketbase /path/to/pb_data # or a PocketBase backup .zip
slonbase serveThe importer reads PocketBase 0.23+ data (data. and storage/) without changing it, and
brings over:
- collections with their ids, fields, rules, indexes and auth options (OAuth2 providers, templates, MFA/OTP settings);
- records with their ids and timestamps, including password hashes, so users sign in with their passwords as before (both use bcrypt);
- superusers (except emails slonbase already has), OAuth2 account links, files (thumbnails are made again on request) and settings (SMTP, S3, backups, rate limits, ...).
Stop PocketBase first, and import into an installation that doesn't have those collections yet. Things it can't carry over are reported instead of failing the import:
- view collections whose queries use SQLite-only SQL (
strftime, ...): recreate them with a PostgreSQL query; - settings encrypted with
PB_ENCRYPTION: set them again in the dashboard; - files kept in S3 by PocketBase: copy them to slonbase's storage under the same
{keys.collectionId}/ { recordId}/
Records are written directly: hooks and realtime events don't run for them. The import is
tested end to end in CI against the PocketBase release (compat/).
The command prints a report: what was imported, and each thing it couldn't carry over, with the reason.
#Pointing your app at slonbase
const pb = new PocketBase("https://new.example.com"); // the only change for most appsThe official JavaScript SDK is tested against slonbase in CI, and the Dart SDK uses the same API. Tokens issued by PocketBase aren't valid on slonbase, so users sign in again. Their passwords keep working.
#JavaScript hooks
Copy pb_hooks/ to slonbase's hooks directory. The extending guide lists
everything that's available. The differences to watch for:
- SQL is PostgreSQL. Queries through
$app.(db( ) newQuery, the query builder,$dbx.) and in view collections run on PostgreSQL, so SQLite-specific SQL (exp strftime,json_extract,datetime(, ...) needs porting. The builder's own SQL ('now') select,$dbx.,hashExp insert, ...) is written for PostgreSQL, andupsertworks. Collections are tables in theslonbaseschema, named after the collection. onModel*hooks see records and collections. PocketBase also runs them for its internal models (logs,_mfas,_otps,_authOrigins); slonbase keeps those in its own tables, outside the hooks. Any hook missing from slonbase fails with aReferenceErrorwhen the file loads, so you'll know right away.- Record writes run in a transaction, so throwing in
onRecordCreateExecuteaftere.rolls the insert back; PocketBase's records API keeps it. A handler that doesn't callnext( ) e.fails the write, where PocketBase skips it and answers as if it happened.next( ) - After-error hooks run in the background, after the rollback, like the after-success hooks; their errors are logged and don't change the response.
onRealtimeConnectRequest'se.returns once the connection is open, where PocketBase's returns when it closes. Code after it runs at connect time.next( ) - Realtime connections have no idle or maximum timeout unless a connect handler sets
e.oridleTimeout e.(PocketBase closes them after 5 minutes idle and 30 minutes in all; the SDKs reconnect either way).maxTimeout $templateescapes everything the same way, where Go escapes by context (inside<script>, URLs, ...). Usejsandurlquerythere.- The engine is QuickJS rather than Goja. It's standard JavaScript (ES2023), with no Go types.
- Go extensions (
main.) can't be carried over. slonbase's equivalent is its Rust library.go
#Migrations
Copy pb_migrations/ to slonbase's migrations directory: PocketBase's migration files,
automigrate output and snapshots run as they are (see migrations). The import
marks PocketBase's applied migrations as applied, so they don't run again.
#Other differences
| PocketBase | slonbase | |
|---|---|---|
| Database | Embedded SQLite | Your PostgreSQL 14+ server (not bundled) |
| Instances | One | Any number on one database, behind any load balancer (several instances) |
| Your data | SQLite file | PostgreSQL tables you can query, replicate and back up with the usual tools |
| Write durability | synchronous = NORMAL | synchronous_commit = on by default (details) |
| Backups | Zip with the SQLite file | Zip in slonbase's own format, plus any PostgreSQL backup tool |
| Settings encryption | PB_ENCRYPTION | SLONBASE_ENCRYPTION_KEY, in the same format; it also covers the token signing secret (encrypting the settings) |
| Migrations | Go/JS migrations, automigrate | JS migrations and automigrate; Go migrations aren't supported |
| Rate limits | Per process, off on new installations | Per instance, on for new installations (an import keeps PocketBase's setting) |
| Dashboard | PocketBase's | slonbase's own, with the same features |
A few answers differ on purpose:
- A number field given something that isn't a number (
"x"): PocketBase stores 0, slonbase refuses it with a validation error on that field. - Computed view columns (
count(,*) a + b,CASE .) get a field of their PostgreSQL type (number, text, bool, date) where PocketBase often gives a JSON field. Columns taken straight from a collection are copies of its fields in both.. .
Error messages and validation codes follow PocketBase (e.g. validation_required,
validation_min_text_constraint), so form errors in your UI keep working.