How to Migrate from Supabase to StitchDB (Step by Step)

To migrate from Supabase to StitchDB, create an empty StitchDB project, open Database › Import from Postgres, choose the Import from Supabase tab and paste a Supabase personal access token plus your database password. StitchDB copies your schema, data, auth users (with their password hashes), buckets, files, cron jobs and Edge Functions, and keeps the copy live until you switch over. Your app code mostly stays the same, because StitchDB speaks the same APIs as the open-source Supabase stack.

This guide walks through the whole move: what to prepare, how the live copy works, what you still have to do by hand, and how to switch over with a minute of downtime or less.

#Why the move is mostly configuration

StitchDB runs the same open-source building blocks a Supabase project uses: PostgREST for the REST API, GoTrue-compatible Auth, the Storage API, Realtime and Deno Edge Functions. The compatibility page lists what works unchanged: REST queries and filters, RLS policies with auth.uid() and auth.jwt(), the anon, authenticated and service_role roles, storage policies, Realtime channels, and Deno functions written with Deno.serve.

That means the migration has two parts:

  1. Copying the data and settings, which the import wizard does.
  2. Pointing your app at a new URL and new keys, which is usually two or three environment variables.

What does change is anything that talks to Supabase's own platform: the Supabase CLI, the Supabase Management API, and dashboard integrations. Those have StitchDB equivalents, the StitchDB CLI and the Management API.

#Before you migrate from Supabase: a checklist

Have these ready before you open the wizard:

  • An empty StitchDB project. The import refuses a project that already has tables, users or files. In the dashboard, choose Import a project next to New project.
  • A Supabase personal access token. Create one at supabase.com/dashboard/account/tokens. StitchDB uses it to list your projects and read settings through the Supabase Management API.
  • The database password of the Supabase project. The Management API cannot read it. If you lost it, reset it in the Supabase dashboard under Database › Settings. Resetting signs out every tool and script that used the old password, so plan for that.
  • Your secret values. Edge Function secrets, OAuth client secrets and your SMTP password cannot be read through any API. Keep them in a password manager or .env file so you can paste them.
  • A rough plan for downtime. A live copy needs only a short write freeze at the end. A one-time copy needs writes stopped for the whole copy.

Pick a region for the new project with care. The region decides where your database and files are stored and cannot be changed later. StitchDB runs in EU data centers (Germany and Finland), so if most of your users are in the US or Asia, factor in the extra latency before you commit.

#Step 1: Run the Supabase import wizard

Open the new project's Studio and go to Database › Import from Postgres. Import from Supabase is the first tab.

  1. Paste the access token and choose Find projects.
  2. Pick the project. Studio shows its region, Postgres version, Edge Functions, secret names, buckets and custom domain.
  3. Enter the database password.
  4. Choose How to copy (Automatic is fine) and Check project.

The check connects to the database and reports what is there: Postgres version, size, schemas, tables and row counts, auth users, files, cron jobs, and anything that needs attention, such as an extension that is not available, a table without a primary key, or a custom role that will not be created.

#How StitchDB connects to Supabase

StitchDB tries the direct connection first (db.<ref>.supabase.co, port 5432). Only that connection can carry a live copy, because the Supabase connection pooler does not pass logical replication. The direct connection uses IPv6 unless your Supabase project has the IPv4 add-on.

If StitchDB cannot reach the direct connection, it falls back to the session pooler and does a one-time copy instead. The check tells you which path it used. If your Supabase project has network restrictions, allow the address the check shows, or turn restrictions off during the import.

#Secrets

Secret values cannot be read through the Supabase API, only their names. The wizard lists them as NAME= lines. Fill in the values and they are set as StitchDB Edge Function secrets. Any you skip end up on the checklist at the end.

The token and password are stored encrypted on your project's server only while needed and are never written to logs. Revoke the Supabase token once the check is done.

#Step 2: Let the live copy catch up

Choose Start live copy. The schema is copied first: tables, views, materialized views, functions, triggers, types, policies and sequences. Then every table with a primary key is copied and followed, so inserts, updates and deletes on Supabase arrive in StitchDB within a second or two.

Studio shows how many tables are done and how far behind the source the copy is. Your app keeps running on Supabase the whole time.

A few details worth knowing:

  • Replication slot. While the live copy runs, your Supabase database keeps a replication slot, which holds back its change log. Switch over or undo within a few days so the Supabase disk does not fill up.
  • Tables without a primary key are not followed live. They are copied at switch-over. If you have large tables without one, add a primary key before you start, or expect a longer final step.
  • Cron jobs are scheduled only at switch-over, so they never run twice.

#Step 3: Point your app at StitchDB

While the copy is live, prepare the app change. In most apps it is an environment variable change:

Terminal
# before
SUPABASE_URL=https://abcdefghijklmnop.supabase.co
SUPABASE_ANON_KEY=eyJhbGciOi...

# after
SUPABASE_URL=https://<project-ref>.p.stitchdb.com
SUPABASE_ANON_KEY=<stitchdb-anon-key>

supabase-js keeps working with the new URL and key. If you want StitchDB-specific features (agent keys, organizations helpers and so on), switch to the StitchDB SDK. It has the same methods:

JavaScript
import { createClient } from 'stitchdb-js'

const stitchdb = createClient(
  'https://<project-ref>.p.stitchdb.com',
  process.env.STITCHDB_ANON_KEY
)

const { data, error } = await stitchdb.from('todos').select('*')

Find the new API URL and keys under Settings › API, and the Postgres connection string under Settings › Database. Server code that connects with an ORM uses port 5432 (session mode) or 6543 (transaction mode). See Connect to Postgres.

Edge Functions read the project URL and keys both as STITCHDB_* variables and under the names the Supabase stack uses, so Deno.env.get('SUPABASE_URL') keeps working.

#Step 4: Switch over

When Studio says Caught up:

  1. Deploy the app build that points at StitchDB, or have it ready to deploy.
  2. Stop writes to Supabase. A minute of maintenance mode is enough for most apps.
  3. Choose Switch over.

The last changes arrive, sequences are set to the source's values, tables without a primary key are copied, then auth users, buckets and files, cron jobs and realtime tables. Finally every table is checked: row counts on both sides (exact up to 2 million rows), and for tables up to 20,000 rows the rows themselves are compared with a checksum. Each table is reported as Same rows, Same count or Differs.

Signed-in users have to sign in once more, because open sessions are not copied. Their passwords and user ids stay the same, so foreign keys to auth.users and policies that use auth.uid() keep working.

#What comes over and what does not

ItemWhat happens
Schemas, tables, dataCopied and followed live
Views, functions, triggers, types, policiesCopied with the schema
Auth usersCopied with password hashes, identities and MFA factors
Triggers on auth.usersCreated after users are copied, so profile rows are not duplicated
Buckets and filesCopied through the Storage API with the service role key read for you
Storage policiesRecreated with the same roles and expressions
Auth settingsSite URL, redirect URLs, sign-up rules, JWT expiry, MFA, e-mail templates
Sign-in providersClient IDs copied; you enter each client secret
SMTPServer, port, user and sender copied; you enter the password
Edge FunctionsCode downloaded and deployed with its JWT setting
SecretsNames only; you fill in values
Database webhooksNot copied; set them up again under Database › Webhooks
Custom rolesNot created; their grants and policies are listed

#Step 5: Work through the checklist

After the import, Studio shows a checklist of what is left. Typical items:

  • OAuth providers. Enter each client secret, and in each provider's console add the new callback URL, https://<project-ref>.p.stitchdb.com/auth/v1/callback, next to the old one.
  • SMTP password, if you left it empty. Until then the platform's own e-mail sending is used.
  • Auth hooks, phone sign-in, SSO and CAPTCHA keys. These are listed one by one because they need your input.
  • Custom domain. Point its DNS at the new project when you switch over (Settings › Custom domains).
  • Database webhooks. Recreate them; the checklist names them.

Then test the paths users actually hit: sign up, sign in with each provider, password reset, file upload, one Realtime subscription, and each Edge Function.

#Doing it from the command line

The same import runs from the StitchDB CLI, which is handy for scripted rehearsals:

Terminal
# link the new project first, then
export STITCHDB_IMPORT_SOURCE="postgresql://postgres:<password>@db.<ref>.supabase.co:5432/postgres"
stitchdb import --platform-token <supabase-token> --secrets .env
stitchdb import status        # progress and live copy lag
stitchdb import switch-over   # finish the live copy
stitchdb import undo          # return to an empty project

Reading the connection string from STITCHDB_IMPORT_SOURCE keeps the password out of your shell history. Add --mode dump for a one-time copy.

#Rehearse first, then cut over

A safe pattern for production apps:

  1. Import into a throwaway project with a one-time copy. Run your test suite against it.
  2. Choose Undo import (it empties the project and drops the replication slot), or delete the project.
  3. Import into the real project with a live copy, switch over during a quiet hour.

Keep the Supabase project running, read-only, for a week or two after the move. If something was missed, you can still look it up.

#Trade-offs to be honest about

StitchDB is not the right destination for every Supabase project. Regions are in the EU only, so latency to users far from Europe will be higher. StitchDB is a younger platform than Supabase, with a smaller ecosystem of guides and integrations built for it. And platform-level tooling (the Supabase CLI, Supabase-specific GitHub integrations) has to be replaced by StitchDB's own. If those matter more than EU hosting, the spend cap model or StitchDB's extra features, staying put is a reasonable call. For a broader comparison, see StitchDB vs Supabase.

#FAQ

#Do my users keep their passwords?

Yes. Auth users are copied with their password hashes, identities and MFA factors, and keep the same user ids. They only need to sign in again once, because open sessions are not copied.

#How long is the downtime?

With a live copy, only the write freeze before switch-over, which usually takes seconds to a minute. A one-time copy needs writes stopped for the whole copy.

#Do I have to change my code?

Usually not. Change the project URL and anon key. supabase-js keeps working; stitchdb-js has the same methods if you want StitchDB features.

#What if the import goes wrong?

Choose Undo import. It removes everything the import added and drops the replication slot on Supabase. The source database itself is never changed.

#Can I import a plain Postgres database instead?

Yes. The Import from Postgres tab takes any Postgres 12 or newer connection string. See the import docs and our guide to moving a Neon database.

Ready to try it? Create a free project and run a rehearsal import today.