scribase import
Two shapes share one verb:
scribase import <url>— reads an existing Postgres database and produces a Scribase schema file.scribase import supabase ...— migrates a live Supabase project into a Scribase environment via the management API.
Import a schema from a URL
Connects to an existing Postgres database, introspects its columns, constraints,
and RLS policies, and emits a .scribase schema file.
scribase import <connection-url> [--out FILE] [--partial]Options
| Flag | Description |
|---|---|
--out <file> |
Write output to file instead of stdout |
--partial |
Allow incomplete import (some columns may not be expressible) |
Example
scribase import "postgresql://user:pass@host/db" --out schema.scribaseNotes
- Any column type, constraint, or default that cannot be expressed in the Scribase schema language is printed as a fidelity note. Without
--partial, the command fails if there are any such notes. - Tables without row-level security are printed as security notes — they are imported but the output warns you.
- Use
--partialwhen working with complex legacy schemas and iterating toward full coverage.
Migrate from Supabase
Migrates a live Supabase project into a Scribase environment. The source project's schema and data are read and applied to the destination environment.
scribase import supabase \
--project-ref <supabase-project-ref> \
--org <scribase-org-id> \
--project <scribase-project-id> \
--env <scribase-env-id> \
[--connection-string <url>] \
[--service-key <key>] \
[--dry-run]Options
| Flag | Env override | Description |
|---|---|---|
--project-ref |
— | Supabase project reference ID |
--org |
— | Destination Scribase organization ID |
--project |
— | Destination Scribase project ID |
--env |
— | Destination Scribase environment ID |
--connection-string |
SCRIBASE_SOURCE_CONNECTION_STRING |
Supabase database URL |
--service-key |
SCRIBASE_SOURCE_SERVICE_KEY |
Supabase service role key |
--schemas a,b |
— | Copy only these schemas (default: every application schema) |
--no-storage |
— | Skip buckets and objects |
--dry-run |
— | Plan what would move without touching anything |
--no-wait |
— | Start the import and print its import_id without following progress |
--functions-dir DIR |
— | After the data import succeeds, deploy every edge function in this local supabase/functions directory (ignored with --dry-run) |
--functions-only |
— | Skip the data import and only deploy the functions (--functions-dir defaults to supabase/functions) |
The connection string and service key are secrets. Pass them via environment variables rather than on the command line in production contexts.
Example
export SCRIBASE_SOURCE_CONNECTION_STRING="postgresql://..."
export SCRIBASE_SOURCE_SERVICE_KEY="eyJhbGci..."
scribase import supabase \
--project-ref abcdefghijkl \
--org acme \
--project store \
--env production \
--dry-runThe server returns a report describing what would be imported. Remove --dry-run to execute: the server starts the import in the background, and the command follows GET /v1/imports/{import_id}, printing each phase as it starts, then prints the verified report. Pressing Ctrl-C stops watching, not the import.
Edge functions
# Data first, then every function in the local checkout:
scribase import supabase --project-ref <ref> --org acme --project store --env production \
--functions-dir supabase/functions
# Or only the functions, for example after a data import that already finished:
scribase import supabase --functions-only --org acme --project store --env production \
--functions-dir supabase/functionsFunction secrets are never read from the source. Re-enter them with
scribase secrets set.
What is migrated
In order, each step replay-safe (--resume <import_id> continues from the last committed step):
- Extensions the app schemas use (best effort; any the target cannot install is listed as an action item).
- Schema of every application schema (
publicplus any schema that Supabase or an extension does not own): tables, types, functions and RPCs, views, indexes,ENABLE ROW LEVEL SECURITY. Foreign keys and triggers are held back until after the data, aspg_restoredoes. - Grants: the exact privileges
anon,authenticatedandservice_rolehold on every schema, table, sequence and function (revokes included, so an RPC hidden fromanonstays hidden). - Auth:
auth.userswith ids and bcryptencrypted_passwordhashes byte for byte, plusinstance_id,audandrole(the lookup columns GoTrue uses on sign-in);auth.identities(provider,provider_id,identity_data), so Apple and GooglesignInWithIdTokenusers resolve to the same user id;auth.mfa_factors(TOTP secrets verbatim). - Data, page by page, keyset-paged and idempotent.
- Post-data: foreign keys, table triggers, sequence positions (
setval), and triggers onauth.users/storage.objectsthat call app functions (for exampleon_auth_user_created). - RLS policies on the app schemas and on
storage.objects/storage.buckets, with theirauth.uid()/auth.jwt()expressions unchanged. - Storage: buckets, then each object's bytes through the destination environment's storage API (
x-upsert, so a resume overwrites), then the object's originalowner_id, so owner-based storage policies keep matching. - Verification: per-table row counts and checksums, policy parity, auth user and identity counts, object counts, and an optional login proof.
What is not migrated
- Sessions and refresh tokens. Scribase signs with its own JWT secret, so every existing session ends. Users sign in once more after the switch, with the same password or the same Apple/Google account, and keep their user id and data.
- API keys and the JWT secret. They are never copied. The app gets the environment's own keys (see below).
- Edge function source from the hosted project. The Supabase API cannot reliably hand back function source, so functions move from your local checkout instead: pass
--functions-dir supabase/functions(or run--functions-onlylater) and the CLI deploys each function, honouring_shared,deno.json/ import maps andverify_jwtinconfig.toml. The console import wizard does not move functions yet. - Vault secrets and function env vars (listed by name; re-enter with
scribase secrets set), custom auth hooks, SMTP, and auth provider settings (set them on the environment's Auth page),realtimepublications,pg_cronjobs, and column-level grants. - Keyless tables (no single-column primary key) are protected against duplicates on a resume, not on a second fresh run. Import into an empty environment.
Switch an Expo app to Scribase
The end-to-end procedure for a mobile app (Expo + supabase-js, email/password plus native Apple/Google sign-in):
Create the environment (console, or
scribase env create ...). Open its Storage page once so the storage service creates its schema.Dry run, then import:
Terminal export SCRIBASE_SOURCE_CONNECTION_STRING="postgresql://postgres:...@db.<ref>.supabase.co:5432/postgres" export SCRIBASE_SOURCE_SERVICE_KEY="<source service_role key>" scribase import supabase --project-ref <ref> --org <org> --project <app> --env production --dry-run scribase import supabase --project-ref <ref> --org <org> --project <app> --env productionProceed only when the verdict is
VERIFIED.Configure auth on the environment (Auth page), mirroring the Supabase project's Auth settings:
site_url, the redirect allow list (myapp://**, plusexp://**for Expo Go), and for native sign-in the Apple provider (client id = the iOS bundle id, plus the Services ID if you also use the web flow) and the Google provider (client ids = the web, iOS and Android OAuth client ids, comma-separated). The console also asks for each provider's client secret.Print the app config:
Terminal scribase apps switch <org> <app> --env production # .env lines for Expo scribase apps switch <org> <app> --format json # for scripts scribase apps switch <org> <app> --key publishable # sb_publishable_ key instead of the anon JWTIt prints exactly
EXPO_PUBLIC_SUPABASE_URL=https://<label>.<domain>andEXPO_PUBLIC_SUPABASE_ANON_KEY=<key>, plus the auth setting names above. It never prints theservice_role/sb_secret_key. That key must never ship in an app, because it bypasses row-level security.Update the app: put the two values in
.env(or the EAS environment), rebuild, and ship.createClient(url, key)needs no code change.Cut over: rehearse steps 2 to 5 against a
stagingenvironment first. At cutover, stop writes on the Supabase project (or force-update the old app version), import into the still-emptyproductionenvironment, and release the build that points at it. The URL and key printed in step 4 are fixed once the environment exists, so the build can be prepared before the final import. Existing users see the sign-in screen once.