Migration: differences & gotchas
A migration is never 100% mechanical — systems differ. Here's what to check after an import so nothing surprises you in production.
RLS is enabled, with no policies
ichibase enables Row-Level Security on every table, like a table you create in the dashboard. Imported tables are no exception — and we do not auto-translate your source's policies (Supabase policies are written against auth.uid()and don't map 1:1). So right after import, your tables are reachable only with the service key; the anon/authenticated keys are denied until you add policies. The detected source policies are listed in the migration report as a starting point.
Types & values (Postgres → Postgres)
Column types come across verbatim (Supabase/Postgres → Postgres is 1:1). A couple of edges: jsonb and array columns are preserved; integers larger than 253 should be stored as strings in the source to avoid JSON precision loss (ordinary bigint IDs within that range are fine). Custom extensions, generated columns, and database functions/triggers are not recreated — re-add the ones you need via the SQL editor.
NoSQL → Postgres is inferred, best-effort
Documents have no fixed schema, so when you import Firestore or Mongo into Postgres the CLI scans every document in a collection and infers one column per field:
| Document values | Inferred column |
|---|---|
| All integers | bigint |
| Mixed numbers (int + float) | double precision |
| Booleans | boolean |
| Dates / timestamps | timestamptz |
| Strings | text |
| Nested objects OR arrays | jsonb |
| Type differs across documents | text |
The document id (Mongo _id / Firestore doc id) becomes a id text primary key. Field names are sanitized to valid Postgres identifiers, and subcollections are flattened into separate parent__child tables with a _parent_id reference. Because it's inference, review the generated tables in the dashboard after import.
jsonb, not a Postgres array type. A native array (text[]) must hold one scalar element type and can't hold objects, so a list like messages: [{…}, {…}] is stored as a jsonb JSON array. The column reads as jsonb in the table editor even though the value is an array — query it with JSON operators (->, jsonb_array_elements), not array operators.Reads paginate, they aren't capped
Your plan's “max rows” is a pagination limit on a single PostgREST response, not a cap on table size. Importing hundreds of thousands of rows is fine; clients just page through them.
MongoDB (1:1)
Provide the source database name (--db) — its collections are remapped onto your project's database. Documents restore exactly as dumped (native mongodump / mongorestore). Mongo has no built-in end-user auth, so a pure-Mongo source brings data only by default; to bring accounts, point the bring-your-own-users flags at the collection that stores them (it imports into ichibase Auth, which lives in Postgres for every project).
Firestore costs reads
Extracting Firestore reads every document, which counts as reads on your Google Cloud bill — most visible with --target postgres, which must scan everything to infer the schema. For very large projects the export takes a while; it runs on your machine, not ours.
What isn't migrated
- Storage objects / files — re-upload via the Storage API.
- Edge / serverless functions, scheduled jobs, and webhooks — recreate them.
- RLS policies, DB triggers, views, and custom extensions — recreate the ones you need.
- Active sessions / refresh tokens — users log in again (passwords carry over).
- SQL → NoSQL conversions — not supported (NoSQL → Postgres is, via inference).
Safe to retry
An import is idempotent per table/collection (truncate-and-reload), so a failed run can be retried without duplicating data. It never touches your source.
