Migrate from Neon to StitchDB with a Live Copy

To migrate from Neon to StitchDB, turn on logical replication in your Neon project, copy the direct (non-pooler) connection string of the database owner, and paste it into Database › Import from Postgres in a new StitchDB project. StitchDB copies the schema and data, then follows every change on Neon until you switch over, so the cut-over itself takes seconds.

Neon is plain Postgres, so this is one of the smoother migrations. The steps below take about fifteen minutes of your time plus however long the initial copy runs.

#What you get when you migrate from Neon

Neon gives you a serverless Postgres database with branching and scale-to-zero. StitchDB gives you a dedicated Postgres database per project, plus Auth, a REST and GraphQL API generated from your schema, Storage, Realtime and Edge Functions. Teams usually make this move because they want those backend services in one place, EU hosting, or the spend cap model.

It is also fair to say what you give up. Neon's compute scales to zero and autoscales; a StitchDB project runs on a shared or dedicated server and is billed by plan, not by compute-seconds. Neon's branching is copy-on-write and instant at any size; StitchDB's preview branches are built from your migrations, optionally with masked production data. And StitchDB's regions are in the EU only. If you rely on fast, data-full branches or US regions, weigh that before moving. Our StitchDB vs Neon comparison goes deeper.

#Step 1: Turn on logical replication in Neon

A live copy needs wal_level = logical on the source. In Neon, this is a project setting. According to Neon's logical replication guide:

  • Open your project, go to Settings › Postgres › Logical replication and choose Enable.
  • It restarts all computes in the project, so active connections drop and reconnect.
  • It cannot be turned off again once enabled.

After it is on, check from psql or the Neon SQL editor:

SQL
show wal_level;  -- logical
select rolreplication from pg_roles where rolname = current_user;  -- t

Neon's docs say the default role created with your project, and roles created in the Neon Console, CLI or API, have the REPLICATION privilege. Roles created with SQL do not, and it cannot be granted to them. So use the project's default owner role (or a Console-created role that owns your tables).

If you would rather not enable logical replication (because it is permanent), StitchDB falls back to a one-time copy. You then need to stop writes for the whole copy instead of a few seconds.

#Step 2: Get the direct connection string

Neon shows two kinds of connection strings. The pooled one has -pooler in the hostname; the direct one does not. Logical replication does not work through a connection pooler, and StitchDB's import asks for the direct connection explicitly.

It should look like this:

Terminal
postgresql://neondb_owner:<password>@ep-cool-name-123456.eu-central-1.aws.neon.tech/neondb?sslmode=require

No -pooler in the host. Your role and database names may differ.

If your Neon project uses IP allow rules, allow the address shown in StitchDB's import check, or relax the rules for the length of the import.

#Step 3: Create the StitchDB project

In the StitchDB dashboard, choose Import a project next to New project, or create a project and keep it empty. The import refuses a project that already has tables, users or files.

Choose the region close to your app servers. The region determines where the database and files are stored, and it cannot be changed later.

#Step 4: Check the source

Open the new project's Studio, go to Database › Import from Postgres, paste the connection string, leave How to copy on Automatic and choose Check source.

The check reports:

  • Postgres version and database size
  • Schemas, tables and row counts
  • Extensions that are not available on StitchDB
  • Tables without a primary key
  • Roles that will not be created

Two of these deserve attention before you start:

Tables without a primary key cannot be followed live. They are copied at switch-over instead. Small lookup tables are fine; for a large table, add a primary key first:

SQL
alter table public.page_views add column id bigint generated always as identity primary key;

Custom roles are not created on StitchDB. Grants and policies for them are skipped and listed. Objects are owned by the project's own role. If an app or BI tool used a separate read-only role on Neon, create a new role on StitchDB under Database › Roles after the import. See Schemas and roles.

#Step 5: Start the live copy

Choose Start live copy. StitchDB copies the schema first: tables, views, materialized views, functions, triggers, types, policies and sequences. Then it copies every table with a primary key and keeps following inserts, updates and deletes. Changes on Neon arrive within a second or two. Studio shows how many tables are copied and how far behind the copy is.

Your app keeps running on Neon throughout.

One thing to watch: while the live copy runs, Neon keeps a replication slot for it, which holds back its write-ahead log. Neon bills storage, so a long-running slot can raise your Neon bill and disk usage. Switch over or undo within a few days.

#Step 6: Prepare the app

While the copy catches up, prepare the new configuration. StitchDB connection strings look like this:

Terminal
# session mode: migrations, psql, long-lived servers
postgresql://p_<ref>_owner:<password>@<db-host>:5432/p_<ref>?sslmode=require

# transaction mode: serverless functions, many short connections
postgresql://p_<ref>_owner:<password>@<db-host>:6543/p_<ref>?sslmode=require

If you used Neon's pooled endpoint from serverless functions, the StitchDB equivalent is port 6543 (transaction mode). Plain SET, LISTEN, SQL-level PREPARE and temporary tables do not carry between transactions there; driver-level prepared statements work. See Connection pooling.

If you used Neon's serverless driver over HTTP from edge runtimes, StitchDB has its own serverless driver, and you can also use the generated REST API through the SDK instead of SQL.

#Step 7: Switch over

When Studio shows Caught up:

  1. Stop writes on Neon. A short maintenance mode, or scaling your app to zero for a minute, is enough.
  2. Choose Switch over in Studio.
  3. Deploy or restart your app with the StitchDB connection string.

During switch-over, the last changes arrive, sequences are set to Neon's current values, tables without a primary key are copied, and every table is checked. Tables are counted on both sides (exact counts up to 2 million rows), and tables up to 20,000 rows are compared row by row with a checksum. The result shows Same rows, Same count or Differs for each table.

The live copy's publication and replication slot on Neon are removed when the import ends.

#Step 8: After the move

A short list of follow-ups:

  • Turn on RLS before exposing tables to the API. If you start using the REST API from browsers, enable Row Level Security on every table in public and add policies. Server-side code connecting as the owner role works without it. See Row Level Security.
  • Bring migrations into the CLI. Run stitchdb db pull to capture the current schema as your first migration, then keep changes in files.
  • Check backups. Paid plans get daily restore points and point-in-time recovery (7 days on Pro, 14 on Scale, 30 on Enterprise). See Backups.
  • Keep Neon for a few days in case you need to look something up, then delete the project.

#From the command line

The same steps run from the StitchDB CLI after you link the new project:

Terminal
export STITCHDB_IMPORT_SOURCE="postgresql://neondb_owner:<password>@ep-cool-name-123456.eu-central-1.aws.neon.tech/neondb?sslmode=require"
stitchdb import
stitchdb import status
stitchdb import switch-over

Reading the source from the environment keeps the password out of shell history. The import docs list every flag.

#Troubleshooting

MessageFix
password authentication failedCheck the password; URL-encode special characters (@ becomes %40).
A live copy needs wal_level = logicalEnable logical replication in Neon, or choose a one-time copy.
This project is not emptyImport into a new project, or undo the previous import.
The live copy has not caught up yetWrites are still arriving on Neon. Stop them and try again.

#FAQ

#Is enabling logical replication on Neon risky?

It restarts your computes once and cannot be reversed. It does not change your data. If that is a problem, use a one-time copy instead.

#Can I move Neon branches too?

Each import copies one database. Import your production branch; recreate preview branches on StitchDB from migrations with preview branches.

#What if I am not happy after switching?

The Neon database is untouched by the import, apart from the publication and slot used during the copy. You can point your app back at it, though writes made on StitchDB after the switch would need copying back.

#Does it work with Neon's pooled connection string?

Not for a live copy. Use the direct connection without -pooler in the hostname.

Ready to move? Create a StitchDB project and run the source check; it changes nothing on Neon.