Skip to main content
Percher is still being built, but you can try it out with a free account right now!

Migrating from Supabase

Keep Supabase or switch to PocketBase

Ask your agent
Move my Supabase app over to Percher.Read the guide at percher.app/docs/migrate-supabase
Keep my Supabase, but host the app on Percher.Read the guide at percher.app/docs/migrate-supabase
For agents and developers

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() = column case 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.record
supabase.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).