Migrate a Supabase project without a password reset
One importer run copies the schema, grants, auth users with their bcrypt hashes, data, RLS policies and storage objects, then proves the copy with row counts and checksums. Your app changes two environment variables.
Last checked October 8, 2026.
Before you start
- From your Supabase dashboard: the project ref, the database connection string (Connect panel, session mode), and the service_role key if you want storage files copied.
- A paused free project refuses connections. Restore it in the Supabase dashboard first.
- A Scribase account with one project. The console creates the production environment for you.
- Optional: the scribase CLI and an access token (Account, Access tokens) if you prefer the terminal to the console.
Option 1: the console (no terminal)
Open the Move page in the console and paste the connection string. The move starts on paste and shows tables, rows, users and policies as they land. Tick "Also copy storage files" and add the service_role key to bring buckets and objects too. The source project is only read.
Option 2: the CLI, step by step
Step 1
Set the credentials once
Keep secrets out of shell history by passing them through the environment variables the importer reads.
sh export SCRIBASE_API_URL=https://api.scribase.com export SCRIBASE_ACCESS_TOKEN=scb_pat_... # Account, Access tokens export SCRIBASE_SOURCE_CONNECTION_STRING="postgresql://postgres.<ref>:<password>@aws-0-<region>.pooler.supabase.com:5432/postgres" export SCRIBASE_SOURCE_SERVICE_KEY=... # Supabase service_role key, only for storageStep 2
Dry run
Reads the source only. Prints what will move (tables, rows, users, objects, bytes) and anything a human should look at first, such as a policy that admits every row.
sh scribase import supabase --project-ref <ref> \ --org acme --project app --env production --dry-runStep 3
Run the import
Extensions, schema, grants, auth users (same ids, bcrypt hashes, identities, TOTP factors), data in resumable pages, foreign keys and triggers, RLS policies, then storage. Each step is replay-safe: --resume <import_id> continues after a failure.
sh scribase import supabase --project-ref <ref> \ --org acme --project app --env production \ --functions-dir supabase/functionsStep 4
Read the verification report
Per table: source rows, Scribase rows and a checksum of every row on both sides. Also policy parity, auth user and identity counts, object counts and an optional sign-in with an account you control. The verdict is VERIFIED only when everything matches.
Step 5
Swap the URL and key in your app
scribase apps switch prints the values for your framework. Existing sessions sign in once more; passwords do not change.
sh scribase apps switch acme app --env production --format dotenv > .env.local # NEXT_PUBLIC_SUPABASE_URL=<Project URL> # NEXT_PUBLIC_SUPABASE_ANON_KEY=<publishable key>
Option 3: plain pg_dump, as migrations
If you only need the public schema and its data (no auth users, no storage), you can dump it yourself and apply it as migrations. The migration endpoint lints first, so run without --apply to read the plan.
The linter blocks five patterns that leak data: a public table without RLS and at least one policy, a policy whose predicate is literally true, a write policy without WITH CHECK, a grant to PUBLIC, and a SECURITY DEFINER function. Fix those in the dump (or use the importer above) before you apply.
mkdir -p migrations
pg_dump "$SCRIBASE_SOURCE_CONNECTION_STRING" --schema=public --schema-only \
--no-owner --no-privileges > migrations/001_schema.sql
pg_dump "$SCRIBASE_SOURCE_CONNECTION_STRING" --schema=public --data-only \
--inserts --rows-per-insert=500 > migrations/002_data.sql
scribase migrate acme app production --dir ./migrations # lint and plan
scribase migrate acme app production --dir ./migrations --apply # runWhat you redo by hand
- Signing keys are never copied: Scribase issues new anon and service_role keys so the old and new projects never trust each other.
- Edge function source is not readable from a hosted project. Deploy it from your checkout with --functions-dir (CLI only).
- User-defined secrets are listed by name; set their values again.
- OAuth provider settings: add the new callback URL in Google, GitHub or Apple, then paste the client id and secret in the console.
Questions people ask
Do users have to reset their passwords?
No. auth.users moves with the same ids and the bcrypt encrypted_password hashes byte for byte, along with identities and TOTP factors.
How long does a migration take?
It depends on rows and storage bytes. The dry run prints both before anything is copied, and the import can be resumed, so a dropped connection costs one page rather than the whole run.
Can I run it twice?
Yes. Writes are upserts keyed on the original ids, so a second run converges instead of duplicating rows.
Does my Supabase project change?
No. The importer only reads from the source. Keep it running until you have switched your app and checked the report.