Tutorial: move a Supabase app to Sovaria
A step-by-step move of a real Supabase app: dry run, import, check the data and the rules, keep your users' passwords, switch the app's client code, and plan the cut-over.
This walks through a whole move, from the first dry run to switching traffic. What the importer does and how each piece maps is in Migrate from Supabase or Firebase; this page is the order to do it in, and how to check each step.
#1. Prepare
- Create the Sovaria project in the region nearest your Supabase project, so the copy is fast and latency stays the same.
- Install the CLI and sign in as an owner or admin:
npm install -g @sovaria/cli,sov login,sov scope pick. - Install
pg_dumpandpsql15 or newer. Copy the Supabase database connection string (Project settings › Database) and the service_role key (for storage). - Note what the importer doesn't move: Edge Functions (Deno code), cron jobs, database webhooks and auth email templates. You will recreate those in step 6.
#2. Dry run
export SUPABASE_DB='postgresql://postgres:PASSWORD@db.REF.supabase.co:5432/postgres'
sov import supabase --db-url "$SUPABASE_DB" \
--supabase-url https://REF.supabase.co --service-key "$SUPABASE_SERVICE_ROLE_KEY" --dry-runNothing is written. Read the report: extensions to turn on first (Extensions), policies that will be rewritten (TO authenticated → auth.uid() IS NOT NULL), and anything it can't import.
#3. Import
sov import supabase --db-url "$SUPABASE_DB" \
--supabase-url https://REF.supabase.co --service-key "$SUPABASE_SERVICE_ROLE_KEY"
# parts on their own: --only schema,auth (then --only data,storage)It runs schema, then users, then data, then storage, and writes sovaria-import-report.json. The schema arrives as one migration, so it shows in Version history: see, undo and restore changes and can be rolled back as a unit.
#4. Check that everything arrived
Row counts
Run the same count on both sides and compare.
select relname as table, n_live_tup as rows from pg_stat_user_tables where schemaname = 'public' order by relname;Passwords
Sign in as a test user with their old password through the SDK (
await sovaria.auth.signIn(email, oldPassword)). Supabase's bcrypt hashes are used as they are, so nobody has to reset.Rules
Run a simulation: every table as a visitor, a signed-in user and each role, compared with what your policies intend.
sov sim run --fail-on criticalStorage
Compare object counts per bucket in Storage. Bucket privacy (public or private) is copied.
#5. Switch the app
Table and function calls through supabase-js keep working against Sovaria's REST endpoint (HTTP API). Auth moves to the Sovaria SDK, and supabase-js sends its session token through its accessToken option (supabase-js 2.45 or newer).
import { createClient } from '@supabase/supabase-js'
export const supabase = createClient(SUPABASE_URL, SUPABASE_ANON_KEY)
await supabase.auth.signInWithPassword({ email, password })
const { data } = await supabase.from('notes').select('id, body').order('id', { ascending: false })- Moving queries to
sovaria.data.sqlorsovaria.data.rpcover time is optional. Both run under the same rules. - Storage calls move to
sovaria.storage(Working with files). File paths are kept, so stored paths in your tables still point at the right files. - Add your site's origin under Project settings › API (Allowed origins (CORS) for browser apps) and register mobile apps and redirect URLs under Authentication.
#6. Recreate functions, schedules and webhooks
| Supabase | Sovaria |
|---|---|
| Edge Function (Deno) | A function in Node.js, TypeScript on Bun, Python or Go. Deno.env.get becomes process.env, and Deno.serve becomes export async function handler(event, context) (Functions). |
| pg_cron job | A function schedule (Event triggers). |
| Database webhook | A database webhook or a db.row trigger (Database webhooks). |
| Auth hooks | Auth hooks, including blocking before-sign-up and before-sign-in hooks (Auth event hooks, Blocking hooks (before create, before sign-in)). |
| Email templates | Authentication › Emails (Auth emails). |
#7. Cut over
- The importer copies a snapshot; it doesn't keep the two projects in sync. Plan a short write freeze: put the app in read-only mode, run the data import into a fresh project (or
--only data,storageafter clearing the rehearsal's tables), check counts, then release the new app version. - Rehearse the whole sequence once on a copy so you know how long the freeze takes.
- Keep the Supabase project read-only for a week or two as a fallback before you delete it.
#Coming from Firebase or Supabase?
| Platform | What this is called there |
|---|---|
| Supabase | Supabase migration |
| Sovaria | Tutorial: move a Supabase app to Sovaria |
Moving an existing app? Migrate from Supabase or Firebase imports users (with their passwords), data, files and rules.