Migrate a Neon database to Scribase
Dump with the pg_dump you already have, apply the files as migrations, and point your driver at the new endpoint. Branching, scale to zero and time travel come with you; auth, storage and realtime are added on the same Postgres.
Last checked October 8, 2026.
Before you start
- Your Neon connection string with a direct (non-pooled) host: pg_dump needs a session, not a transaction pooler. In the Neon console, turn off "Connection pooling" when you copy it.
- pg_dump at the same major version as the source or newer (Scribase runs Postgres 17).
- A Scribase project and the CLI with an access token.
- Direct TCP connections into Scribase are not open yet, so this guide loads data through migrations instead of pg_restore. It suits databases up to a few gigabytes; for larger ones, contact us before you start.
Step by step
Step 1
Dump the schema
Drop owners and grants: Scribase sets ownership itself, and the anon and authenticated roles get grants you choose.
sh export NEON_URL="postgresql://<user>:<password>@ep-<id>.<region>.aws.neon.tech/<db>?sslmode=require" mkdir -p migrations pg_dump "$NEON_URL" --schema-only --no-owner --no-privileges \ --exclude-schema=neon_auth > migrations/001_schema.sqlStep 2
Dump the data as INSERT batches
The migration endpoint takes SQL statements, so use INSERT batches rather than COPY blocks.
sh pg_dump "$NEON_URL" --data-only --inserts --rows-per-insert=500 \ --exclude-schema=neon_auth > migrations/002_data.sqlStep 3
Check extensions
List what the source uses and compare with the extensions directory. Add CREATE EXTENSION lines to the top of 001_schema.sql if pg_dump left them out.
sql select extname, extversion from pg_extension order by 1;Step 4
Close every public table to API clients
Neon databases rarely use row level security, but every Scribase project also serves its tables over REST, so the migration linter refuses a public table without RLS and at least one policy. Run this on Neon to write a migration that turns RLS on and adds a deny-all policy for API roles. Postgres does not apply RLS to the role that owns a table (unless FORCE ROW LEVEL SECURITY is set), so server code connecting as the owner is unaffected. Open tables to signed-in users later with real policies.
sh psql "$NEON_URL" -At > migrations/003_rls.sql <<'SQL' select format( 'alter table public.%I enable row level security; ' 'create policy server_only on public.%I as restrictive for all ' 'to anon, authenticated using (false) with check (false);', tablename, tablename) from pg_tables where schemaname = 'public' order by tablename; SQLStep 5
Lint, then apply
Without --apply the server lints and plans only. The linter also blocks grants to PUBLIC and SECURITY DEFINER functions; the --no-privileges dump above leaves grants out, and any SECURITY DEFINER function needs a rewrite or a review before it ships.
sh scribase migrate acme app production --dir ./migrations scribase migrate acme app production --dir ./migrations --applyStep 6
Point your code at Scribase
The environment answers GET /serverless with the connection URI and HTTP endpoint for @neondatabase/serverless. Queries stay the same.
ts import { neon, neonConfig } from '@neondatabase/serverless' neonConfig.fetchEndpoint = () => process.env.SCRIBASE_SQL_HTTP_ENDPOINT! const sql = neon(process.env.DATABASE_URL!)
If you used Neon Auth or the Data API
- Users sync: Scribase keeps neon_auth.users_sync in step with its own users table, so queries that join it keep working.
- Bring your own JWT: list your issuer and REST accepts its tokens; your RLS policies read the claims as before.
- Data API: postgrest-js with only a bearer token works unchanged against the project REST URL.
Verify the copy
Run the same count on both sides for the tables that matter, or compare a checksum of each table.
select 'orders' as t, count(*) from orders
union all select 'customers', count(*) from customers;
-- order-independent checksum of a table
select md5(string_agg(md5(o::text), '' order by md5(o::text))) from orders o;Questions people ask
Can I use pg_restore with a custom-format dump?
Not yet: pg_restore needs a direct Postgres connection, and public TCP access to Scribase databases is still being rolled out. Plain SQL dumps applied as migrations work today.
Will branching work the same way?
Branches are copy-on-write with their own URL and keys, can start from a past point in time, can mask columns, and can expire. The CLI commands differ: see the Postgres branching page.
What happens to my Neon project?
pg_dump only reads. Keep the Neon project until your app has run on Scribase and the counts match.