Migrate from Supabase or Firebase
Move a Supabase or Firebase project to Sovaria with one command: schema and row-level security, users who keep their passwords, data, documents, storage, and a report of anything that needs a look.
What moves: from Supabase, the public schema (tables, views, functions, triggers, RLS policies), auth.users with their ids, verified state and passwords, every row, and storage buckets with their files. From Firebase, Auth users with their passwords, every Firestore collection and subcollection, Cloud Storage files, and a report that maps each security rule to a Sovaria policy.
Users keep their passwords. Supabase stores bcrypt hashes, which Sovaria uses as they are. Firebase uses a modified scrypt: Sovaria checks it with your project's hash parameters (Firebase's own: rounds 8, memory cost 14; higher values are refused) on the user's first sign-in, then stores a bcrypt hash, so nobody has to reset their password. An import never sends emails and never runs sign-up hooks or function triggers.
Prerequisites
- A Sovaria project, and the CLI signed in as an owner or admin of its organization:
pip install sovaria-cli,sov login,sov scope pick. - Supabase: the PostgreSQL client tools (
pg_dump,psql, version 15 or newer), the database connection string (Supabase › Project settings › Database) and, for storage, the service_role key. - Firebase: a service account JSON with read access to Firestore and Storage, the Firebase CLI for
firebase auth:export, and the password hash parameters (Firebase console › Authentication › Users › ⋮ › Password hash parameters).
From Supabase
# 1. check everything, write nothing
sov import supabase \
--db-url 'postgresql://postgres:PASSWORD@db.REF.supabase.co:5432/postgres' \
--supabase-url https://REF.supabase.co --service-key "$SUPABASE_SERVICE_ROLE_KEY" --dry-run
# 2. import (schema, then users, then data, then storage)
sov import supabase --db-url '...' --supabase-url https://REF.supabase.co --service-key "$SUPABASE_SERVICE_ROLE_KEY"How policies change: Sovaria databases have no anon or authenticated role. A policy TO authenticated becomes TO public with auth.uid() IS NOT NULL added to its condition; TO anon adds auth.uid() IS NULL; a service_role-only policy is dropped, because the service key already bypasses row-level security. auth.uid(), auth.jwt() and auth.role() work as in Supabase. GRANTs are dropped: your app's queries run as the project's app role, which can read and write your tables subject to RLS.
Use --only schema,auth to run parts on their own, and --rows-per-insert 1 if a very wide table makes a statement too large.
From Firebase
firebase auth:export users.json --format=json --project YOUR_PROJECT
# hash.json: {"base64_signer_key": "...", "base64_salt_separator": "Bw==", "rounds": 8, "mem_cost": 14}
sov import firebase --service-account sa.json \
--auth-export users.json --hash-config hash.json --rules firestore.rules --dry-run
sov import firebase --service-account sa.json \
--auth-export users.json --hash-config hash.json --rules firestore.rulesHow data is laid out: every document keeps its Firestore path: users/{id} stays in users and users/{id}/orders/{id} stays a subcollection of that user. Ids with characters Sovaria keys don't allow are cleaned, and the report lists them. Firebase uids aren't UUIDs, so users get new ids; the import writes sovaria-import-uid-map.json and, by default, replaces old uids with the new ids in your documents and in document ids and paths, so users/{uid} is still the user's own document (--keep-uids to turn that off). Import Auth first so the map exists.
Rules: with --rules, sovaria-rules-report.md lists every rule with its Sovaria equivalent (public, signed-in users, owner only, or a custom policy). Nothing is applied automatically: add the policies, then check them with a simulation (Tutorial: prove your table is private before you ship).
After the import
- Read
sovaria-import-report.json: skipped users (already in the project), failed rows, policy notes, renamed keys. - Run
sov sim run --fail-on criticalto prove each kind of caller reaches only what it should. - Point your app at Sovaria. From Supabase, keep supabase-js: change the URL and anon key to your project's, and
from()/rpc()calls work as before (see HTTP API, "Tables over REST"). From Firebase, keep your Firestore code and change its import to@sovaria/sdk/firestore(see Firebase-style code (Firestore API)); Auth and Storage calls move to the Sovaria SDK — see Quickstart.