scribase
Migrate from Postgres

Point it at a connection string. Nothing else to collect.

A Neon branch, a Render or RDS instance, or any self-hosted Postgres database has no project ref and no service key alongside it, just a connection string — so that is all the importer asks for. Schema, grants, data, sequences, extensions and RLS policies move the same way they do from a Supabase source, then the same fail-closed verification report proves it: row counts and checksums for every table, policy parity.

Four steps in the console: connect the source, review a dry run that writes nothing, run it, and read the verification report. Your place is saved if you close the tab; credentials never are.

What moves

Everything a Postgres app runs on

Schema
Tables, types, functions, views, indexes, and extensions, applied in dependency order.
Grants
Privileges on every schema, table, sequence and function, carried over exactly.
Data
Every table in the schemas you choose, copied in resumable, idempotent pages.
Row level security
Every policy re-applied after the data lands, then compared one for one against the source.

What stays the same

  • Row data and constraints, byte for byte, with a checksum proving it
  • Foreign keys, triggers, and sequence positions, restored after the data
  • Safety on a second run: every write is an upsert, so re-running never duplicates a row

What you do by hand

  • API keys and the JWT secret are never copied; the app gets the destination environment's own keys.
  • pg_cron jobs, realtime publications, and column-level grants are not moved — recreate them by hand.
  • There is no auth.users or storage.objects to move, because a plain Postgres database has no Auth or Storage API of its own; add Scribase Auth and Storage to the same database once the data lands.
How it works

Plan, run, prove

  1. Step 1

    Plan it

    The dry run reads the source only. It prints the tables, rows, and extensions that will move, plus any finding a human should see first.

    export SCRIBASE_SOURCE_CONNECTION_STRING="postgresql://user:pass@host/db"
    
    scribase import postgres \
      --org acme --project app --env production --dry-run
  2. Step 2

    Run it

    Drop --dry-run. Extensions, schema, grants, data, foreign keys and triggers, then RLS policies move in that order, each step idempotent so a resumed run never duplicates a row.

    scribase import postgres \
      --org acme --project app --env production
  3. Step 3

    Read the report

    The verdict is fail-closed: VERIFIED only when every table matches on row count and checksum and every policy is present. Anything else is MISMATCH or UNVERIFIED, with the failing tables named.

  4. Step 4

    Point your code at Scribase

    Swap the connection string (and, for a serverless driver, the HTTP endpoint) for the destination environment's own. Nothing about your queries changes.

The verification report

Old versus new, table by table

Every import ends with this. The verdict is VERIFIED only when every row count and checksum matches, every policy is present, and the sign-in proof passes. The numbers below are an example of the format.

TablePostgres rowsScribase rowsChecksum
public.profiles12,40812,408 match
public.orders88,21488,214 match
public.order_items240,977240,977 match
Also checked: RLS policy parity, auth user count, storage object count, and a sign-in with an account you control. Verdict: VERIFIED
Migration help

A large or unusual move? Tell us about it.

The importer runs in the console today. If your app is large or you want a second pair of eyes on the dry run, tell us your size and we will reach out to help.

FAQ

Migrating from Postgres

Do I need a project ref or a service key?

No. A plain Postgres database has no platform API alongside it, only a connection string, so that is the only credential the importer asks for.

Does this work from Neon, Render, RDS, or my own server?

Yes, all of them — only the connection string's host changes. The importer runs server-side against whatever Postgres host you give it.

Can I import only part of my database?

Yes. Pass --schemas with a comma-separated list to copy only those schemas; by default every non-system schema is discovered.

Is it safe to run twice?

Yes. Every write is an upsert keyed on the original ids, and an interrupted run resumes with --resume <import_id> from its last committed step.