Database Migration
Schema-aware, FK-safe row migration into any Postgres.
Unbase reproduces your Supabase Postgres data in a target Postgres without touching the source.
Extract
extract introspects the source and writes the IR:
- Columns — name, data type, and underlying udt (so arrays, json, and bytea are handled correctly on load).
- Foreign keys — used to compute a FK-safe load order (referenced tables before referencing ones). Self-references are ignored; true cycles are appended and flagged.
- Primary keys — used for idempotent loads.
- Rows — every table is dumped to
db/rows/<table>.json, withbigintandbyteakept JSON-safe.
Load
load reads the IR and inserts rows into an existing target schema, in FK-safe
order. It is:
- Idempotent —
INSERT ... ON CONFLICT (pk) DO NOTHING, so re-runs and resumes are safe. - Id-preserving — original primary keys are inserted verbatim so foreign keys line up.
- Type-aware — base64 →
bytea, JSON objects →json/jsonb, arrays and ISO timestamps pass straight through.
Schema
Unbase can recreate the target schema for you from the snapshot:
npx unbase schema # write .unbase/ir/schema.sql
npx unbase schema --apply # run it against the target
npx unbase schema --orm drizzle # write a Drizzle schema.ts instead
npx unbase schema --orm prisma # write a schema.prisma instead
npx unbase load --create-schema # create, then load, in one stepThe emitted DDL is self-sufficient and idempotent: it includes required
CREATE EXTENSION (e.g. vector, uuid-ossp), CREATE TYPE for enums, every
table with primary keys, and foreign keys added last (so order and cycles don't
matter). Or skip this and create the schema with your own Drizzle/Prisma migrations
— Unbase loads into whatever already exists.
Not yet emitted: check constraints, non-PK indexes, sequences/identity,
generated columns, views, RLS, and triggers. varchar/numeric lengths
aren't preserved.
Verify
After loading, Unbase reconciles per-table counts between the snapshot and the
target. actual >= expected passes (tolerating pre-existing rows); anything short
is reported as a gap.
npx unbase verifyKnown limitations
GENERATED ALWAYS AS IDENTITYcolumns are not special-cased yet.- Sequences are not resynced after load (resync manually if you rely on serial ids).