Migrating from Supabase
Keep Supabase or switch to PocketBase
Two options: keep using your Supabase project as an external database, or switch to Percher's managed PocketBase. For most apps — and anything that leans on Postgres features — Option 1 is the right call.
Option 1: Keep Supabase (recommended, no rewrite)
# percher.toml [data] mode = "supabase" [data.supabase] url = "https://your-project.supabase.co" anon_key = "eyJ..." # No code changes needed — your existing Supabase client keeps working. # SUPABASE_URL and VITE_SUPABASE_URL are injected automatically.
This works for every Supabase app — you keep Postgres, RLS, auth, storage, edge functions, realtime, and pgvector exactly as they are. Percher just hosts the app.
Option 2: Switch to PocketBase
Percher generates most of the migration for you. It writes a ./migration-preview/ folder — nothing touches your data until you run the scripts yourself.
bunx percher migrate-from-supabase --project <ref> --token sbp_... # → migration-preview/pb_schema.json tables → PocketBase collections # → migration-preview/pb_migrate.js runnable data-import script (copies your rows) # → migration-preview/MIGRATION_NOTES.md every RLS policy + manual step # Also convert your Supabase SDK calls to the PocketBase SDK: bunx percher migrate-from-supabase --project <ref> --token sbp_... --rewrite-client # preview first; add --apply to write in place (originals are backed up)
Honest limits — read before committing
- Auth users can't be moved directly.Supabase password hashes aren't compatible with PocketBase — every user has to reset their password.
- It's a preview plus a script, not one click. You run the data-import yourself with a Supabase service-role key and review the flagged items.
- Some things don't port at all: Edge Functions, Postgres views/triggers/functions, complex RLS (the tool translates the simple
auth.uid() = columncase and flags the rest), realtime, PostGIS, and pgvector. If your app depends on these, keep Supabase (Option 1).
Concept mapping for the parts you rewrite by hand:
supabase.from('table').select()pb.collection('table').getList()supabase.from('table').insert({})pb.collection('table').create({})supabase.from('table').update({})pb.collection('table').update(id, {})supabase.from('table').delete()pb.collection('table').delete(id)supabase.auth.signUp()pb.collection('users').create({})supabase.auth.signInWithPassword()pb.collection('users').authWithPassword()supabase.auth.getUser()pb.authStore.recordsupabase.storage.upload()pb.collection('x').create(formData)supabase.channel().subscribe()pb.collection('x').subscribe('*', fn)Row Level Security (SQL policies)PocketBase API rules (per collection).select('*, posts(*)')pb.getList({ expand: 'posts' })Not portable: Edge Functions (use API routes instead), Postgres views/triggers (rewrite as app logic), PostGIS (not available), Supabase Vector/embeddings (use an external service).