How to move a Supabase project to another host

By the SupaHost team · Updated 11 November 2026 · 8 min read · View as Markdown

Moving a Supabase project means dumping and restoring the database, which carries your schema, data, RLS policies and Auth users with their passwords, then copying Storage files, redeploying Edge Functions and re-creating the settings a dump doesn't hold. With the Supabase CLI and psql it takes an hour or two for a typical project, plus a short write freeze for the final cutover.

This guide is for moving to any host that runs Supabase: another Supabase Cloud project, your own self-hosted server, or a managed host. Commands are taken from Supabase's backup and restore guide, checked in November 2026.

What has to move

Item Where it lives Carried by a database dump?
Schema and data Postgres Yes
Auth users and password hashes auth schema Yes (data dump)
RLS policies, custom roles, grants Postgres Yes; role passwords must be reset
Your changes to auth and storage (policies, triggers) Postgres Partly; check with supabase db diff
Storage buckets and file records storage schema Records yes; the files themselves no
Storage file contents Object storage No
Edge Functions and their secrets Functions runtime No
pg_cron jobs cron schema No
Vault secrets Encrypted in Postgres Not usable; the new host has a different key
Auth settings (site URL, redirects, providers, SMTP, templates) Platform config No
Extensions, Realtime publications, database webhooks Postgres / platform Re-enable manually
App environment variables Your app You update them

Supabase stores passwords as bcrypt hashes in auth.users, so moving the rows moves the passwords; users don't need to reset them (Supabase troubleshooting guide). Users who signed in with OAuth have no password and simply sign in again once the provider is configured on the new host.

Before you start

  • Install the Supabase CLI, Docker (the CLI runs pg_dump in a container) and psql. Your psql version should be at least as new as the newer of the two Postgres servers.
  • Get the old project's connection string from the Connect panel. Use the direct connection; if your network has no IPv6, use the session pooler string instead.
  • Create the new, empty project on the target host and get its Postgres connection string.
  • Export Vault secrets and cron jobs now, while the old project is running (see step 3).

Step 1: dump the database

Run these three commands against the old project:

supabase db dump --remote="$OLD_DB_URL" -f roles.sql --role-only
supabase db dump --remote="$OLD_DB_URL" -f schema.sql
supabase db dump --remote="$OLD_DB_URL" -f data.sql --use-copy --data-only \
  -x "storage.buckets_vectors" -x "storage.vector_indexes"

The schema dump skips Supabase-managed schemas (auth, storage and extension schemas), because the target already has them. The data dump includes rows from those schemas, which is how users and bucket records come across. data.sql contains password hashes, so treat it like a credentials file and delete it when you're done.

Step 2: restore into the new host

psql \
  --single-transaction \
  --variable ON_ERROR_STOP=1 \
  --file roles.sql \
  --file schema.sql \
  --command 'SET session_replication_role = replica' \
  --file data.sql \
  --dbname "$NEW_DB_URL"

Everything runs in one transaction, so a failure leaves the target untouched. Setting session_replication_role = replica disables triggers during the load so they don't fire on restored rows. If you hit permission errors about supabase_admin or cli_login_postgres, the Supabase guide's troubleshooting section lists which lines to comment out.

The target must be a Supabase Postgres (self-hosted, Supabase Cloud or a managed Supabase host), because the dump expects roles like anon, authenticated and service_role and the managed schemas to exist.

The plain pg_dump alternative

If you only need your own tables, for example to move them to a non-Supabase Postgres, standard tools work:

pg_dump "$OLD_DB_URL" --schema=public --format=custom --no-owner -f public.dump
pg_restore --dbname "$NEW_DB_URL" --no-owner --single-transaction public.dump

This leaves out auth and storage, so it won't move users. Foreign keys pointing at auth.users will fail unless that table exists on the target.

Step 3: re-create what the dump doesn't cover

Run these on the new database after the restore.

Extensions, Realtime and webhooks. Enable any non-default extensions you used, add tables back to the Realtime publication, and re-create database webhooks:

alter publication supabase_realtime add table public.messages;

Custom role passwords. Roles with LOGIN come across without usable passwords:

alter user "reporting_user" with password 'a-new-strong-password';

Changes to auth and storage. Link the CLI to the old project (supabase link --project-ref <old-ref>) and run supabase db diff --remote > changes.sql to capture policies on storage.objects and triggers on auth.users, then apply what's missing.

Cron jobs. On the old project, list them:

select jobname, schedule, command from cron.job;

Then re-create each one on the new project with cron.schedule('jobname', '*/5 * * * *', $$ ... $$). Jobs that call Edge Function URLs need the new URL. More in Supabase pg_cron.

Vault secrets. The new host encrypts with its own key, so copy values rather than ciphertext. On the old project, select name, decrypted_secret from vault.decrypted_secrets;, then on the new one select vault.create_secret('<value>', '<name>'); for each.

Step 4: move Storage files

The data dump moved bucket and file records, but not the bytes. Supabase's guide links a Node.js script that copies buckets and files between projects using both projects' service keys. The idea fits in a few lines of supabase-js:

import { createClient } from '@supabase/supabase-js'

const src = createClient(process.env.OLD_URL, process.env.OLD_SECRET_KEY)
const dst = createClient(process.env.NEW_URL, process.env.NEW_SECRET_KEY)

async function copy(bucket, prefix = '') {
  const { data: items } = await src.storage.from(bucket).list(prefix, { limit: 1000 })
  for (const item of items) {
    const path = prefix ? `${prefix}/${item.name}` : item.name
    if (!item.id) { await copy(bucket, path); continue } // a folder
    const { data: file } = await src.storage.from(bucket).download(path)
    await dst.storage.from(bucket).upload(path, file, { upsert: true })
  }
}

const { data: buckets } = await src.storage.listBuckets()
for (const b of buckets) await copy(b.id)

Add error handling and pagination for folders with more than 1,000 files before using it for real. Check that bucket settings (public flag, size limit, allowed MIME types) match on the target.

Step 5: redeploy Edge Functions

Download each function from the old project, then deploy it to the new host:

supabase functions download my-function --remote=<old-project-ref>

Import maps and deno.json aren't downloaded, so restore them from your repo. On another Supabase Cloud project, deploy with supabase functions deploy. On a self-hosted stack, copy the function folder into the stack's volumes/functions directory and restart the functions container if needed. Other managed hosts have their own deploy route. Then set function secrets again: supabase secrets list shows names, not values, so take the values from your password manager or deployment config.

Step 6: Auth settings and app config

None of this is in the database, so copy it by hand:

  • Site URL and redirect URLs, including preview and mobile deep-link URLs.
  • OAuth providers: client IDs and secrets, and add the new callback URL (https://<new-api-host>/auth/v1/callback) in each provider's console.
  • SMTP settings and email templates.
  • JWT and API keys: the new project has its own keys, so existing sessions end and users sign in again. Supabase notes that reusing the old JWT secret keeps tokens valid, if the target lets you set it.
  • App environment variables: the Supabase URL, publishable/anon key, secret/service-role key and any database URL, in every environment and CI.

Cutover checklist

  1. Rehearse. Do a full run into a throwaway project, time it, and fix errors.
  2. Prepare DNS. If you use a custom domain, lower its TTL a day ahead. A custom domain also saves you shipping a new mobile build; see Supabase custom domains.
  3. Freeze writes. Put the app in maintenance or read-only mode. Downtime is roughly the time for the final dump and restore.
  4. Final dump and restore, then compare row counts on key tables, including auth.users and storage.objects.
  5. Switch config: env vars, DNS, OAuth redirect URLs. Redeploy the app.
  6. Test: password login, OAuth login, magic link email, file upload and download, a Realtime subscription, each Edge Function, and a cron run.
  7. Keep the old project for a few days. Pause writes there, but don't delete it until you're sure.
  8. Set up backups on the new host and test a restore. See Supabase backups.

Option: one-click import into SupaHost

If the target is SupaHost, the importer on /migrate/supabase-cloud does steps 1, 2 and 4 for you. It copies the database in one transaction, Auth users with their passwords and identities, RLS policies and roles, and Storage buckets and files.

It does not copy Auth settings (site URL, redirect URLs, provider keys), Edge Functions, pg_cron jobs or Vault secrets; it lists these so you can re-create them. Edge Functions are deployed from the dashboard's Functions tab or over MCP, since supabase functions deploy isn't supported. Direct Postgres access for a manual psql restore needs the Business plan (over an SSH tunnel). Also check limits before moving: Storage files are capped at 50 MB each, and there are no image transformations, read replicas or connection pooler. After import, daily backups and exports are on the Backups tab (/docs#backups).

FAQ

Can I transfer a Supabase project to another account without migrating?

Yes, if it stays on Supabase Cloud. Supabase's project transfer moves a project between organizations without touching the database. Migration is only needed when changing host or region.

Will my users have to reset their passwords?

No. Password hashes are stored in auth.users and move with the data dump. They will usually have to sign in again, because the new project signs tokens with different keys.

Does supabase db dump include Storage files?

No. It includes bucket and file records from the storage schema, but not the file contents. Copy the files separately with a script that downloads from the old project and uploads to the new one.

How long does a Supabase migration take?

The dump and restore scale with database size and network speed; for small databases they are often quick, and most of the time goes on configuration and testing. Rehearse once to get a real number for your write-freeze window.

Can I migrate with zero downtime?

Not with dump and restore, which needs a short write freeze. Near-zero downtime requires logical replication from the old database to the new one, which is more complex and needs direct access to both databases.

Running several Supabase projects? SupaHost gives you your own server with the open-source Supabase stack and 5 to 30 projects for one monthly price. See plans or compare costs.