/migrate/supabase/ · From Supabase to Cratefield
Move from Supabase in one click.
Point it at a Supabase project and it would move the lot onto Harness on Postgres: your users with their passwords, your tables and rows with PostGIS, your files into R2, and then a switch you can undo. It starts by reading, never writing, and tells you what moves on its own and what will not.
Not one click yet. The first step, a read-only inspection of your project, is Shipping: merged in #697 and runnable from source, not yet in an fz release. The other four are Designed in public issues and unbuilt, and the one-click button belongs to the managed service, which is Planned. Each step below says where it stands.
The five steps
Inspect, then users, then data, then files, then the switch.
Each step would be resumable and safe to run twice, and nothing would be written anywhere until you approve the plan the first step produces. The design is ADR 0026, from #657.
Shippingmerged and tested; from source until the next fz release Designedpublic issues, unbuilt Plannednamed, unspecified
- 01InspectShipping
- 02Auth usersDesigned
- 03Schema and dataDesigned
- 04StorageDesigned
- 05Cutover, with rollbackDesigned
-
What it would do
Connect with a read-only role and read the catalogue: schemas, tables with row counts and sizes, columns, constraints, indexes, sequences, enums, extensions, functions, triggers, views, row-level security policies, your users and the sign-in providers in use, and storage buckets with their object counts and sizes. Given a Management API token, Edge Functions and Realtime publications too. It writes a report, in Markdown and JSON: what moves on its own, what needs work, and what blocks.
What you keep
- Your Supabase project, exactly as it was. Inspect only reads: a read-only role, one read-only snapshot, nothing created on the source.
- Your credentials, out of the report and out of the logs. The report names users by their Supabase id only, never an email or a hash, so you can share it.
-
What would move
Every Supabase user: email, whether it is verified, when the account was made, and the user and app metadata carried across as it is. The Supabase user id is kept as the external id, so the next step can point your tables’ foreign keys at the right accounts. Google, Apple and other OAuth identities map to the matching sign-in provider where you have one configured.
What you keep
- Password hashes, so no forced resets. Supabase’s bcrypt hashes would be accepted as they are and upgraded to Argon2id the next time each user signs in (#650).
- Verification state: an unverified user stays unverified.
Not carried: sessions, so everyone signs in once more after the switch, with the same password. Phone sign-in, MFA factors and SAML are listed in the report rather than moved. A user whose provider is not configured can sign in by magic link.
-
What would move
Tables, columns, constraints, indexes, sequences, enums and extensions (PostGIS, citext, uuid-ossp, pgcrypto) into a schema named
appin Harness Postgres, kept apart from the modules’ own tables. Functions, triggers and views copied as written, where they do not reach into Supabase’s auth or storage. The rows streamed across withCOPY, in chunks that resume where they stopped. Then a check against the source: row counts and a checksum per table, every foreign key validated, every sequence carrying on where Supabase left off.What you keep
- Your schema and every row, PostGIS geography columns included.
- Row-level security, as a reviewed list of checks. Each policy would be mapped to its table, command, roles and expression, with a suggested check in code and a failing test stub. Harness keeps customers apart with a database each rather than with policies, so it does not copy them as enforcement, and it does not guess a translation.
-
What would move
Every bucket and every object, read through the Storage API and copied to R2 with its content type, then checked by count, size and checksum.
What you keep
- Your storage objects, in R2, under the same keys:
<bucket>/<object name>, so the paths you stored still point at the right file. - Each bucket’s public flag and its storage policies, named in the report.
Not carried: image transforms. An object over 10 MiB is copied and stays readable, but module code cannot replace it through the
Blobport, which caps a write at that size. - Your storage objects, in R2, under the same keys:
-
What would happen
You pause writes on Supabase. Anything that changed since the copy is copied again and checked again, and then the switch is made: the runtime and database binding, DNS and the base URL. It refuses while a check fails, or while anything in the report still has no decision against it.
What you keep
- Your Supabase project, untouched, for a rollback window of 14 days by default. Rolling back is switching back, with a report of what was written on the Harness side since.
- A backup of the Harness side taken at cutover, and a decommission checklist that is generated for you and never run for you.
Not in the first version: syncing writes back to Supabase automatically. If you roll back, writes made after the switch are reconciled by hand from that report.
The one click would be a “Migrate from Supabase” button in the Cratefield dashboard Planned, which would run the same five steps with an “Approve plan” stop and a progress view. The command line, fz import supabase, would drive the same engine, so the two cannot drift apart. The dashboard is a preview today, not something you can log into.
What the inspect report shows Illustration
Three piles: moves on its own, needs work, blocks.
An illustration of a made-up project, not output from a real run. The real report, from #697, uses the same three piles; here is a sample from its test project.
# Migration report: acme-prod (illustration) # Read-only, one snapshot. Nothing written. Moves automatically 42 tables, 1,284,310 rows, 3.1 GB 6,412 users with their password hashes 3 buckets, 18,204 objects, 7.4 GB Needs work 57 RLS policies on 31 tables, 4 Edge Functions Blocks 2 fix these before a plan can be applied
Moves automatically auto
Copied and verified without a decision from you.
- 42 tables · 1,284,310 rows
- extensions: postgis, citext, uuid-ossp
- 18 functions · 9 triggers · 4 views
- 6,412 users · 5,980 with a password
- Google identities → configured provider
- 3 buckets · 18,204 objects
Needs work review
Each line needs “covered” or “waived, because” before cutover.
- 57 RLS policies → 57 checks, 57 test stubs
- 12 uses of auth.uid()
- 2 SECURITY DEFINER functions
- 4 Edge Functions
- 1 Realtime publication: messages
- 214 GitHub sign-ins, no provider set
- supabase-js in your clients
Blocks stop
A plan cannot be applied until these are fixed.
- extension pgroonga is not on the target Postgres
- view public.member_emails reads auth.users
What needs manual work
The parts that are code, not data.
These are listed in the report, never guessed. Each one needs a decision before the cutover will run: covered, with the code or test that covers it, or waived, with a reason.
Edge Functions → routes
Rewritten as routes in a module
An Edge Function is TypeScript on Deno; a Harness route is Rust inside a module, compiled into your backend. The report lists each function, if you give it a Management API token; without one it says “not inspected” rather than “none”. The rewrite is yours. What a module is.
Realtime → the Realtime port
Re-subscribed through the Realtime port
Publications and channels are listed. The Realtime port with its in-process adapter Shipping runs on the native runtime, which is where an imported project lands. The Durable Object adapter for the edge is Designed, #103.
RLS → checks in code
A review, one policy at a time
Every policy becomes its own line with a suggested check and a failing test stub, written into the run directory and never into your source. No bulk waiver: each policy needs its own reference or its own reason. Until every line is covered, what protects the imported tables is that Harness exposes no direct table access. Why a database each instead.
supabase-js → your routes
Your client code changes
supabase-js talks to PostgREST, and Harness does not emulate it, so those calls become calls to your backend’s own routes. The importer never rewrites client code; removing supabase-js is on the decommission checklist.
Also reported: auth.uid() and auth.jwt() usage, SECURITY DEFINER functions, grants to anon and authenticated, pg_cron jobs, webhooks and pg_net, foreign keys into auth, and Supabase-only extensions such as pg_graphql and pgsodium, which are skipped.
Try the inspect step Shipping
One command, read-only.
Merged in #697, not yet in an fz release, so for now you build it from the harness repository with the import-supabase feature. It reads your project inside one read-only transaction and writes the report as Markdown or JSON. See a sample report.
It reads a database URL for a read-only role from SUPABASE_DB_URL, and optionally a Management API token from SUPABASE_ACCESS_TOKEN to see Edge Functions and auth settings. Neither ever appears in the report. Nothing touches a target until you apply an approved plan, and that part is Designed.
# not in an fz release yet: build it from the harness repo
cargo install --git https://github.com/Cratefield/harness \
cratefield-cli --features import-supabase
export SUPABASE_DB_URL=postgres://readonly:…@db.<ref>.supabase.co:5432/postgres
fz import supabase inspect --project <project-ref> --md --out report.md
Questions
What people ask before they move.
Is there downtime?
A short write freeze, not an outage. Before the switch you pause writes on Supabase, with its maintenance mode or your own. The importer copies again whatever changed since the main copy, checks it again, and then switches. How long that takes depends on how much changed, so there is no figure to promise.
A zero-downtime switch would need replication rights on your Supabase database, which would break the rule that the source is only ever read, so it is left until after the first version.
Can I roll back?
Yes, for 14 days by default, and you can make the window longer. The Supabase project is never written to, so it is still there exactly as you froze it. Rolling back is switching back. You get a report of what was written on the Harness side since the switch, including users who changed their password, to reconcile by hand; automatic reverse sync is not in the first version.
Is my data safe while it moves?
The source is only read: a read-only role, a read-only transaction at one snapshot so every table is read from the same moment, and only GET calls to Storage. Nothing is created on your Supabase project, not even a publication.
Your keys are held in memory for the run and never written to the plan, the report or the logs; the command line stores them nowhere. Nothing reaches the target until you approve a plan, and the switch refuses while any check fails.
What does the first step change?
Nothing. Inspect reads your project and writes a report on your machine. It is useful on its own: you learn what would move, what needs work and what blocks before you decide anything. It is the step you can run today, from source: see how.
Do my users have to reset their passwords?
No. Their bcrypt hashes would move as they are and be upgraded on their next sign-in (#650). They do sign in once more after the switch, because sessions are not carried across.
What does it cost?
The importer would be part of Harness, which is MIT licensed, so running it yourself against your own Postgres costs nothing beyond your own infrastructure. The one-click button would be part of the managed service, which is Planned and is not something you can pay for yet. The intended launch price, and the arithmetic behind it.
Can I do it today?
Not with this tool. Nothing on this page is released. If you need to move now, Supabase’s own export and a hand-built move are the honest answer, and the comparison names the cases where staying on Supabase is right.
Managed · Planned
Hear when it works
Join the waitlist and say you are coming from Supabase. We reply once, when there is something to run.
Join the waitlistOpen source · In the open
Follow the build
The design and every step are public issues. Or write to us about your project at [email protected].