Skip to content
Vibe Coders

Migrating Off Supabase: When and How

Signals you have outgrown Supabase and the honest migration path to managed Postgres plus a real auth provider, including the zero-downtime playbook most guides get wrong.

By WitsCode11 min read

Supabase is one of the best ways to start a product. A team can go from empty repo to production auth, Postgres, storage, and realtime in a weekend. The flip side of a platform that does a lot for you is that the ceiling, when you hit it, tends to arrive all at once. Compute costs start doubling every few months. A prospect asks for customer-managed keys. Your European users wait 180ms for every write. The product is working, and the infrastructure is working, but the two are pulling in opposite directions.

This article is about when to migrate off Supabase, what to migrate to, and how to do it without taking the product down or spending three months on plumbing. It is also about when not to migrate, because most teams reading this do not actually need to. The honest version of the answer is more useful than the aspirational one.

Real signals you have outgrown Supabase

Four signals mean it is time to evaluate a migration. Any one is enough; most teams that migrate have two or three at once. The first is cost, not "it feels expensive" cost, but a real bill you can tie to line items. The second is multi-region: if you need writes close to users on different continents, Supabase cannot give you that today. The third is bespoke schema and extension pressure, which shows up as a custom extension you cannot install, a replication slot that keeps choking, or an RLS model gone unmaintainable. The fourth is compliance: if a customer's security review asks for things your plan does not offer and the next plan up is a five-figure jump, the math gets interesting.

What is not a reason to migrate: a bad week, a single incident, or vague unease about vendor lock-in. Supabase runs on Postgres with standard extensions. Lock-in is lower than most alternatives. Migrate because a spreadsheet and a real user need said so, not because of a Hacker News thread.

The cost math: where the curve actually bends

Supabase pricing on the Pro plan starts at twenty-five dollars per project per month, but the real cost is compute. The compute ladder climbs quickly once you leave the Micro and Small tiers. A Medium instance is sixty dollars per month. An XL is two hundred and ten. A 4XL is seven hundred and thirty-five. A 16XL, which is not unusual for a product doing real transactional work, is over three thousand seven hundred per month for compute alone, before storage, egress, realtime messages, and function invocations.

Here is the trigger most teams miss. Somewhere between fifteen hundred and two thousand dollars per month on a single project, you are paying a managed-service premium on top of what is, underneath, Postgres with PgBouncer and GoTrue. That premium is worth it early. It stops being worth it when your bill crosses five thousand dollars per month. At that point, running the same workload on Aurora or Neon or a Crunchy Bridge cluster, plus a separate auth provider, is almost always cheaper. Not marginally cheaper. Often half the cost, sometimes a third.

The inflection is not just the bill. It is the team time spent on compute rightsizing, figuring out why Realtime is burning credits, chasing storage egress, and waiting on support tickets for things a DBA could answer in ten minutes. By the time you are paying five thousand a month, you are also paying attention to the platform, and that attention has a cost too.

Below a thousand dollars per month, migration makes no financial sense. You will spend more in engineering time than you will ever recover. Between one and three thousand, it depends on how much of that bill is compute you could right-size inside Supabase first. Above three thousand, the conversation is real. Above five thousand, you are almost certainly leaving money on the table by staying.

The multi-region wall

Supabase runs a project's primary in a single AWS region. Read replicas exist on the Team tier and above, and they are fine for read-heavy workloads with users clustered near the primary. They are not a solution for multi-region writes. If a user in Frankfurt writes to your database, that write goes to your primary in us-east-1 and takes the full round-trip. There is no primary-write multi-region option today, and the infrastructure work to add one is substantial enough that it is not going to ship in the next quarter.

This is a structural limit, not a pricing one. No Enterprise contract unlocks it. If your product genuinely needs sub-100ms writes in multiple continents, you are going to leave Supabase.

The honest follow-up is that most teams who think they need multi-region actually need read replicas near their users plus a disaster recovery region. Aurora Global Database handles that within Postgres, with typical cross-region replication lag under five seconds. PlanetScale Postgres on the Metal tier does similar with regional read replicas and fast failover. Neon supports read replicas across regions. CockroachDB, Spanner, and YugabyteDB do actual multi-region writes if you truly need them, with the tradeoffs that distributed SQL brings.

Before you migrate for multi-region reasons, write down the specific latency your users are experiencing and the specific latency you need. If the gap is "writes are 180ms and need to be under 50ms globally," you have a real problem. If the gap is "we might expand to Europe next year," you do not, yet.

Schema, extension, and RLS ceilings

Supabase supports a generous list of Postgres extensions: pgvector, PostGIS, pg_cron, pg_net, and most of the usual crowd. What it does not support are extensions that require deep system access or conflict with Supabase's own tooling. TimescaleDB in its full form is not available. Citus is not available. If your product is built on one of those, you were probably never going to stay on Supabase long-term anyway.

The other ceiling is Realtime. Supabase Realtime uses a replication slot on your Postgres instance. On high-write tables or very wide rows, that slot can fall behind, and everything that depends on it degrades. Teams running analytics workloads on the same database as their user-facing transactional tables tend to hit this first.

Row Level Security is the third pressure point. For a simple multi-tenant app where every row has an owner_id, the mental model is clean and policies write themselves. For hierarchical teams, impersonation, delegated admin, or complex tenancy with cross-tenant sharing, RLS policies turn into a web of auth.uid() joins and helper functions that become their own maintenance burden. The failure mode is not that RLS stops working. It is that every feature ships slower because another policy needs updating. If you are hitting two of these ceilings, your schema is probably the reason to migrate.

Picking a destination: managed Postgres options

Once you decide to leave, the destination question is its own conversation. There is no single best answer. There are good answers for specific workloads.

AWS RDS Postgres is the boring choice, and boring is a feature. Predictable pricing, decades of operator experience in the hiring pool, works with every tool on earth. Aurora Postgres sits above RDS and decouples storage from compute, with six-way replication across three availability zones. Aurora is better for bursty read workloads and faster failover. RDS is cheaper for steady state. If you want cross-region disaster recovery with low replication lag, Aurora Global Database is the path.

Neon is Postgres reimagined as a serverless service with branching and scale-to-zero. Compute autoscaling actually works. The branching model is genuinely useful for preview environments and dev workflows, and the cost profile is excellent for spiky traffic. If your workload is steady and heavy, Neon's compute unit pricing can get expensive, but for most products, it undercuts RDS.

PlanetScale launched Postgres in 2024 with a Metal tier that runs on local NVMe rather than network storage. For heavy transactional workloads where IO latency matters, this is the strongest option on the market today. Pricing is per-cluster and not cheap, but the performance is a different category from anything else in managed Postgres.

Crunchy Bridge is managed Postgres without a mystery layer. It behaves like vanilla Postgres because it is vanilla Postgres. Teams that want a DBA-friendly environment with pg_partman, pg_stat_statements, and a responsive operator tend to land here. Less flashy than Neon or PlanetScale, very solid in production.

For most teams leaving Supabase, the shortlist is RDS or Aurora if you are already on AWS and want boring, Neon if you want developer experience and spiky-traffic economics, or PlanetScale Metal if transactional performance is the deciding factor.

Replacing Supabase Auth without a forced reset

Supabase Auth is GoTrue, running against the auth schema in your Postgres. When you leave Supabase, you need somewhere for user login to live. The good news is that password migration is almost always possible without forcing users to reset.

Supabase stores password hashes as bcrypt. Bcrypt is a standard, portable format. Auth.js, the open source option, accepts bcrypt natively through its credentials adapter. Clerk accepts bcrypt imports through their migration endpoint. WorkOS does the same. You export the auth.users table, transform the format to match the target provider's import schema, and upload. Users log in next time with the same password. No email blast, no support tickets, no forced reset.

The provider choice depends on what you are optimizing for. Auth.js is free, self-hosted, and flexible; you run it, which is both the benefit and the cost. Clerk is managed, has excellent developer experience, and costs roughly two cents per monthly active user after the free tier. For a product with twenty thousand MAU, that is around two hundred dollars per month, which is often still less than the Supabase Team tier. WorkOS is the right answer if your customers are enterprises that require SAML single sign-on, SCIM directory sync, and a signed enterprise agreement. AuthKit from WorkOS gives you a full auth UI out of the box. For B2B with enterprise requirements, WorkOS is hard to beat.

The rule of thumb: if you are B2C or SMB B2B, Clerk or Auth.js. If you are moving upmarket and selling to IT buyers, WorkOS. If you want to self-host everything, Auth.js or Keycloak.

The zero-downtime migration using logical replication

The migration itself is mechanical. Skip steps and you will have a bad week. Do them in order and the cutover takes seconds.

First, stand up the destination Postgres instance. Match the Postgres major version to what Supabase is running. Do not try to upgrade and migrate at the same time. Second, run pg_dump against Supabase with schema-only, no-owner, no-privileges flags. Apply that schema to the destination. Strip the Supabase-specific pieces you do not need. The auth schema can be kept temporarily if you want to run the hybrid state described below. The storage schema only matters if you are using Supabase Storage. Triggers referencing supabase_functions come out.

Third, set up logical replication. Supabase runs Postgres with wal_level set to logical, so you can create a publication on the source and a subscription on the destination. The subscription does an initial data copy and then streams changes. For very large tables, a faster pattern is to take a snapshot with pg_dump, load it in parallel, then start the subscription from the snapshot's LSN. This cuts the initial copy time substantially.

Fourth, verify. Row counts per table. Sequence values, which do not replicate through logical replication and must be reset with setval after cutover. Extension state. Index presence. Constraint validation. Run a read-only smoke test against the destination while replication is live.

Fifth, cutover. Flip the application's DATABASE_URL to the destination in a single deploy. If you are using connection pooling, update that too. The actual switch is seconds. Monitor replication lag in the minutes before cutover to pick a low-traffic moment. After cutover, drop the subscription on the destination so writes to the new database do not try to reconcile with the old one.

A note on RLS. Policies replicate with the schema but reference auth.uid(), which only exists inside Supabase's auth schema. On the destination, either keep the auth schema temporarily and populate it from your new provider, or replace the policies with a session-variable pattern where your application sets a local GUC at the start of each transaction. Most teams end up moving authorization into the application during this migration, which is fine; the RLS-everywhere model was a starting choice, not a forever choice.

The auth-cutover order most guides get wrong

Almost every migration guide tells you to move the database and the auth provider at the same time. This is wrong, and it is the single biggest reason Supabase migrations go badly.

The correct order is database first, auth second. Here is why it works.

In step one, you migrate the database to your destination Postgres. Supabase Auth stays pointed at the old Supabase project and keeps issuing JWTs. Your new backend trusts those JWTs by verifying them against Supabase's JWKS endpoint or the shared secret. From the user's perspective, nothing has changed. From your perspective, the heavy migration is done and you can prove the new database under real load. Run in this hybrid state for two to four weeks. Watch replication behavior, connection pooling, query plans, and the things that only break in production.

In step two, once the database is stable and you trust it, migrate auth. Export the auth.users table, import it into Clerk or Auth.js or WorkOS, switch the application to verify tokens from the new provider, and turn off Supabase Auth. Users log in next session. No password reset.

Doing these together means two systems change at once. If anything breaks, you do not know which migration caused it. Doing them in sequence means that when the new auth provider has its first hiccup, you know the database is solid and you can focus on the auth issue. Teams that follow this order have boring migrations. Teams that do both at once have war stories.

When not to migrate

The last section is the one WitsCode spends the most time on with clients. If your Supabase bill is under two thousand dollars per month, migration almost never pays back. The engineering time to do it well, two to four weeks of focused work plus a stabilization period, costs more than you will save in the first year. The right move is to rightsize compute, turn off features you are not using, consolidate projects, and stay on the platform that is working. If you are under one hundred thousand monthly active users and have no compliance or multi-region requirement, migration is almost certainly premature. Supabase scales further than most teams give it credit for, once you know where the knobs are. And if you are migrating because of a single incident or a vague sense that you should be on "real infrastructure," stop and write down the actual problem in one sentence with numbers in it. If you cannot, you are not ready.

WitsCode runs migration engagements for teams that have genuinely outgrown Supabase, with a scope covering assessment, destination selection, cutover plan, execution, and a thirty-day stabilization window. We say no to more of these than we say yes to. If the math does not work, we will tell you. If it does, the playbook above is how we run them, and it is boring on purpose, because boring is what you want on the day your production database moves.

Get weekly field notes.

Practical writing on shipping products, straight to your inbox. No spam.

Need help with this?

MVP Development

We design and build web apps, MVPs, and SaaS products. Talk to us about what you are working on.

Talk to us

Want to discuss vibe coders for your business?

Start a project and we'll talk through where you are, what's working, and the highest-leverage moves for the next 90 days.