Keep your app, your users, and their ids.
Export the app with its admin token, and Scribase moves every namespace, every user, and every file into Postgres with the same ids, so links keep resolving and users sign in by email code like before.
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.
Everything a InstantDB app runs on
- Namespaces
- Each namespace becomes a table with a uuid primary key and a jsonb data column holding every attribute.
- Links
- Exported as arrays of linked ids inside data, so data->'owner' joins back to the linked table.
- $users
- Every user with the same id and email, marked confirmed, ready for email-code and magic-link sign-in.
- $files
- Every file at the same path in a private files bucket, checked with SHA-256.
What stays the same
- Entity ids, so every link and every stored reference still resolves
- User ids, so rows that point at a user still point at the same person
- Email sign-in: users get a code or a magic link, as on Instant
What you do by hand
- Instant permission rules are not SQL. Tables start with row level security on and no policies; port each rule to an RLS policy.
- If you used Instant OAuth, enable the same providers on Scribase. Users are linked by email on their first OAuth sign-in.
- Realtime queries become Postgres changes over Scribase Realtime; subscribe with the JavaScript client.
Plan, run, prove
Step 1
Export with the admin token
The scribase package calls the same admin query endpoint the Instant admin SDK uses, flattens links to id arrays, and downloads every $files object.
import { writeInstantExport } from 'scribase/importers'; await writeInstantExport({ appId: process.env.INSTANT_APP_ID!, adminToken: process.env.INSTANT_ADMIN_TOKEN!, namespaces: ['todos', 'projects'], links: { todos: ['owner'], projects: ['members'] }, directory: './instant-my-app', });Step 2
Plan, then run
Upload instant-export.json in the console wizard, or place the folder in the server exports directory to include files.
await scribase.imports.createFrom({ source: 'instantdb', sourceInstantAppId: process.env.INSTANT_APP_ID!, sourceExportDir: 'instant-my-app', destination: { organizationId: 'acme', projectId: 'app', environmentId: 'production' }, });Step 3
Read the report
Every namespace compared by row count and checksum, plus user and file counts. The verdict is fail-closed.
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.
| Table | InstantDB rows | Scribase rows | Checksum |
|---|---|---|---|
| public.profiles | 12,408 | 12,408 | match |
| public.orders | 88,214 | 88,214 | match |
| public.order_items | 240,977 | 240,977 | match |
Want us to host it? Join the InstantDB waitlist.
The importer runs today on any Scribase you operate. Hosted Scribase opens by source, in waitlist order. Tell us your size and we will reach out when your slot opens.
Migrating from InstantDB
Do users need new accounts?
No. Each user keeps the same id and email and signs in with an email code or magic link. Instant never stored passwords, so there is nothing to reset.
Is the data still queryable like InstaQL?
It is Postgres: query with SQL or the JavaScript client, and subscribe to changes with Scribase Realtime. Links become id arrays you can join on, and you can normalize hot paths into real columns with ordinary migrations.