Supabase → Neon
Move the Supabase public schema to Neon with pg_dump. Leave auth, storage, realtime, and internal schemas behind unless you have a separate auth plan. Legacy Neon Auth could import bcrypt hashes. Managed Better Auth cannot. Remap user ids if you move auth at all.
Should you make this move?
Both platforms have a case. Compare what you gain with what you give up before scheduling the cutover.
Supabase
- PostgreSQL, authentication, storage, realtime, and edge functions offer an open backend stack
- Managed infrastructure removes a large amount of operational work
- Production scaling and service integration still demand database and infrastructure judgment
- Platform-specific services can create architectural lock-in
Neon
- Serverless PostgreSQL with branching and scale-to-zero creates excellent developer workflows
- A mature data model and query ecosystem supports serious production workloads
- Platform-specific branching, compute behavior, and pricing require workload-aware design
- Schema design, migration, tuning, and operations require specialist attention
Neon: Serverless PostgreSQL with branching and scale-to-zero creates excellent developer workflows. This removes a major source-side concern: Production scaling and service integration still demand database and infrastructure judgment.
What you lose: PostgreSQL, authentication, storage, realtime, and edge functions offer an open backend stack. What you inherit: Platform-specific branching, compute behavior, and pricing require workload-aware design.
Jump to a section
Know the shape of the move.
This timeline assumes
- You are moving application data in public (and other schemas you own). Neon says auth, storage, realtime, and internal schemas are not a straight copy.
- Neon’s 2026 guide still documents legacy Neon Auth for password-hash import. New Neon projects use Managed Better Auth, which cannot import Supabase password hashes. Those users create new accounts or use OAuth.
- Dump and restore use a direct Supabase connection on port 5432, not the transaction pooler on 6543.
- If you migrate auth, Neon says user_id values are reassigned and you must remap foreign keys.
- Supabase stays recoverable until Neon has passed one application cycle.
What survives the move.
“Partial” and “manual” are not footnotes. They are work that must be scheduled and verified.
| Item | Outcome | Impact | What happens | Mitigation |
|---|---|---|---|---|
| public schema data | partial | critical | Tables, indexes, constraints, sequences, views, and functions in public can dump and restore. That is the part Neon treats as portable. | Dump only the schemas you own. Restore to Neon. Compare counts and foreign keys. |
| auth schema and users | manual | critical | auth.users does not become Neon Auth by restore. Legacy Neon Auth could import email plus bcrypt hashes. Managed Better Auth cannot import those hashes. | Decide auth before cutover. If you stay on Better Auth, plan password resets or OAuth. If you still have legacy Neon Auth, follow Neon’s hash import and then remap ids. |
| User id remapping | manual | critical | Neon says auth import assigns new user_id values. Rows in public that store Supabase user ids will point at the wrong person until you remap. | Build a map from old id to new id and rewrite every user-keyed table before you open writes. |
| RLS policies | partial | critical | Policies that call auth.uid() or Supabase roles do not mean the same thing on Neon until roles and auth functions exist. | Rewrite policies against the Neon auth model you actually use. Test as a normal user, not as the restore role. |
| Storage objects | lost | high | storage.objects metadata is not your files. Buckets stay in Supabase until you copy bytes to Neon-compatible storage or another bucket. | Inventory buckets and copy objects with a tool that preserves paths you still reference. |
| Realtime, Edge Functions, and cron | lost | high | Those products do not restore from pg_dump. | Replace them with Neon-side jobs, workers, or a separate queue before cutover. |
| Supabase roles and Data API | manual | critical | anon, authenticated, and the Data API are Supabase. Neon has its own roles and, if you enable it, a Data API. | Create the roles your policies need and do not expose a wide API until RLS is rewritten. |
| Internal Supabase schemas | lost | medium | Dumping everything including auth, storage, and realtime internals usually breaks a restore on Neon. | Exclude those schemas from the production dump. |
| Transaction pooler dumps | manual | high | pg_dump over port 6543 is a common failure. | Use the direct 5432 connection for dump and restore. |
| Password hashes on Better Auth | lost | critical | Neon states Managed Better Auth cannot import Supabase password hashes. | Tell users they will reset passwords or sign in with OAuth. Do not claim silent password reuse. |
| Supabase backups and PITR | lost | medium | Those backups stay in Supabase. | Keep your own pg_dump and turn on Neon backups before traffic moves. |
| Connection strings and clients | manual | critical | Every server, edge function, and local .env still pointing at Supabase will split writes. | Inventory every URL and switch them in one change window. |
Where each thing goes.
| Source | Destination | Method | Notes |
|---|---|---|---|
| public tables | Neon public tables | transform | Dump only owned schemas. |
| auth.users | Neon Auth or a new auth store | manual | Better Auth cannot take bcrypt hashes. |
| Supabase user id | Remapped Neon user id | transform | Rewrite every FK after auth import. |
| RLS policy using auth.uid() | Rewritten Neon policy | manual | Test as a normal user. |
| storage bucket | New object store | manual | Copy bytes, not only metadata. |
| Direct 5432 URL | pg_dump source | transform | Do not use 6543. |
| Sequences | Restored sequences | transform | Insert a row and check the next value. |
| Edge Functions / cron | Replacement workers | manual | Nothing in the dump starts them. |
| Application DATABASE_URL | Neon URL after verification | manual | Switch every consumer. |
Make the move recoverable.
Create the source-of-truth backup
Keep a schema-scoped dump and an auth decision before you touch Neon.
- List public tables, user-keyed foreign keys, buckets, functions, and cron jobs.
- Decide legacy Neon Auth versus Managed Better Auth versus an external IdP.
- Dump owned schemas from the direct 5432 endpoint.
- If you still use legacy Neon Auth, export emails and bcrypt hashes with the function Neon publishes.
- Hash the dump and store it read-only.
Proof to capture: A dated manifest lists schemas, dump hash, auth path, and bucket inventory.
Schema-only dump
Avoid restoring Supabase internals onto Neon.
- Use the direct URL.
- Exclude auth, storage, and realtime unless you have a written exception.
- Restore into an empty Neon database.
Proof to capture: Save the input, output, command or tool settings, warnings, and final item counts.
Auth remap
Stop user-keyed rows from pointing at new random ids.
- Import users only on the auth product that can accept them.
- Build old_id → new_id.
- Rewrite every user-keyed table and then drop the map from production if you do not need it.
Proof to capture: Save the input, output, command or tool settings, warnings, and final item counts.
The things most likely to hurt.
These are operating limits. Treat every “Stop if” condition as a blocked migration, not a suggestion.
User ids are not remapped
Auth import succeeded and public rows still store Supabase uuids.
- Consequence
- Users see the wrong data or none.
- Mitigation
- Remap every user-keyed table before you open writes.
Stop if: A production row still points at a Supabase user id after auth import.
Better Auth cannot take hashes
The Neon project is on Managed Better Auth and the plan assumed silent password import.
- Consequence
- Nobody can sign in with their old password.
- Mitigation
- Plan resets or OAuth, or keep an IdP that already holds the users.
Stop if: Password login is required and hashes cannot be imported.
Dump used the transaction pooler
pg_dump points at port 6543.
- Consequence
- The dump is incomplete or fails.
- Mitigation
- Use port 5432 for dump and restore.
Stop if: The dump was taken through the transaction pooler.
Do the work in this order.
- Days 1–4 · inventory
Inventory and auth decision
8–14 hours active2–4 days elapsedAuth and storage owners waiting- List schemas, user FKs, buckets, and jobs.
- Sign the auth path.
- Pick dump schemas.
Depends on: Supabase and Neon admin access
Stop / go checkpointDump?
Go when: Auth path and schema list are signed.
Stop when: Password login is required and no hash path exists.
- Days 3–6 · backup
Dump
4–8 hours active1–3 days elapsedDump runtime waiting- Dump owned schemas from 5432.
- Export hashes only if legacy Neon Auth is in play.
- Hash files.
Depends on: Approved inventory
Stop / go checkpointRestore?
Go when: Dump hashes and counts look complete.
Stop when: The dump includes internals you cannot restore.
- Days 6–14 · pilot
Restore and remap
16–36 hours active5–9 days elapsedAuth import waiting- Restore to a Neon branch.
- Import users if the auth product allows it.
- Remap ids, rewrite RLS, copy a storage sample.
Depends on: Verified dump
Stop / go checkpointCut over?
Go when: Counts, logins, and RLS pass.
Stop when: A user-keyed table is unmapped.
- Days 14–18 · cutover
Freeze and switch
6–16 hours active1–3 days elapsedWrite freeze waiting- Pause Supabase writes.
- Dump and restore production.
- Remap ids and switch URLs.
Depends on: Passed pilot
Stop / go checkpointOpen writes?
Go when: Neon is the only writer and logins work as planned.
Stop when: A FK still stores a Supabase user id.
- Days 17–22 · observe
Observe and close
6–11 hours active4–6 days elapsedApplication cycle waiting- Watch auth, RLS, and jobs.
- Keep the Supabase project.
Depends on: Verified cutover
Stop / go checkpointClose rollback?
Go when: No trigger fires through one cycle.
Stop when: Auth or data access regresses.
Cut over with a way back.
Cutover
Make Neon the only writable application database after a frozen, remapped restore.
- Pause Supabase writes and cron.
- Dump owned schemas from port 5432.
- Restore to Neon and remap user ids if auth moved.
- Rewrite RLS and switch every DATABASE_URL.
- Keep Supabase recoverable.
Proof to capture: Neon serves the app. Counts match. Logins follow the signed auth path. No writer remains on Supabase.
Rollback
Return writes and login to Supabase without losing reviewed Neon-era rows.
- Stop writes to Neon.
- Point the app and auth at Supabase.
- Export Neon rows written after cutover.
- Apply only reviewed rows to Supabase.
- Run the same login and data checks on Supabase.
Proof to capture: Supabase is writable again and Neon is not receiving production writes.
- Unmapped user id
- Password login broken
- RLS failure
- Dump taken through 6543
- Missing public table
Prove the migration worked.
Every blocking check must pass. Capture the evidence before cleanup begins.
| Pass | ID | Check | Method | Expected result | Evidence |
|---|---|---|---|---|---|
V-01Blocking | Owned schemas only | Inspect pg_dump args and the archive TOC. | auth, storage, and realtime internals are absent unless signed. | Dump TOC. | |
V-02Blocking | Row counts and FKs | Compare public counts and foreign-key totals. | Every scoped table matches or has a signed exception. | Count sheet. | |
V-03Blocking | Signed login path | Sign in with password and OAuth for a seed user. | The result matches the Better Auth, legacy, or external plan. | Login log. | |
V-04Blocking | User id remap | Join a user-keyed table to the mapping table. | No production row still stores a Supabase user id after auth import. | Remap query. | |
V-05Blocking | RLS | Query as a normal user and as a denied user. | Policies match the Neon auth model. | Policy test. | |
V-06Blocking | Storage sample | Open representative objects from the new store. | Required bytes exist at the referenced paths. | Object manifest. | |
V-07Blocking | Port 5432 | Inspect dump and restore URLs. | No transaction pooler 6543 on dump/restore. | Config audit. | |
V-08Blocking | Supabase still up | Point a staging app at Supabase. | The old URL still works. | Rollback note. |
Remove the scaffolding safely.
Safe after: One application cycle, Neon backups on, and seven stable days.
- Archive the dump, hash file, and remap SQL.
- Revoke temporary Supabase dump roles.
- Keep Supabase until billing and rollback close.
- Remove unused Data API keys.
- Document the Neon URL and auth product.
- Schedule the next backup and RLS review.
When the plan met reality.
First-hand accounts are preferred. Vendor case studies are labeled, and every note below is an editorial paraphrase—follow the link for the full context.
Shreyash Raj moved Quaslation from Supabase Postgres to Neon for branching, serverless cost, and hosting control. He documented tables first, exported each table to JSON in foreign-key order, imported with session_replication_role = replica, reset sequences, and verified counts. A pg_dump of Supabase sat behind hot, staged, and partial rollback plans. The final npm run db:migrate:full export, import, and verify step is what he ran after a staging rehearsal.
- Export tables in dependency order and disable triggers during import, then reset sequences before you open writes.
- Rehearse on a Neon branch and keep a Supabase pg_dump so a hot rollback is a connection-string change.
- Same-engine Postgres still needs an ordered import. Foreign keys will stop a naive load.
- The destination win was Neon branching and cost, not a different SQL dialect.
Verify against the primary material.
Platform behavior changes. Check these sources and the review dates above before executing a production migration.
- Neon: migrate Supabase database and authAccessed 2026-10-29
- Supabase: connecting to PostgresAccessed 2026-10-29
- PostgreSQL: SQL dumpAccessed 2026-10-29