# How to move a Supabase project to another host

> To move a Supabase project, dump and restore the database (users included), copy Storage files, redeploy functions, then redo cron, secrets and Auth settings.

Source: https://supahost.app/guides/migrate-supabase-project · Updated 2026-11-11

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](/guides/self-host-supabase), or a managed host. Commands are taken from Supabase's [backup and restore guide](https://supabase.com/docs/guides/platform/migrating-within-supabase/backup-restore), 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](https://supabase.com/docs/guides/troubleshooting/migrating-auth-users-between-projects)). 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](https://supabase.com/docs/guides/local-development/cli/getting-started), 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:

```bash
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

```bash
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](https://supabase.com/docs/guides/platform/migrating-within-supabase/backup-restore) 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:

```bash
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:

```sql
alter publication supabase_realtime add table public.messages;
```

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

```sql
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:

```sql
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](/guides/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:

```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:

```bash
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](/guides/supabase-custom-domain).
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](/guides/supabase-backups).

## Option: one-click import into SupaHost

If the target is [SupaHost](/pricing), the importer on [/migrate/supabase-cloud](/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](/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](https://supabase.com/docs/guides/platform/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.
