Open Finapp
Development

Data Migration History

Historical overview of the Finapp backend migration - from Firebase to Supabase + PowerSync.

Finapp's backend was migrated from Firebase Realtime Database to Supabase Postgres + PowerSync (offline-first sync) with Supabase Auth (email/password). This page documents that migration and the script that imports a Firebase JSON export into Supabase.

The legacy Firebase (v6) source code lives on the firebase branch.

What changed

EntityFirebase (v6)Current (Supabase + PowerSync)
Accountsaccounts with string keyswallets table, text ids
CategoriesString IDs, parentId as stringtext ids, parentId = a category id or null (root)
TransactionsString IDs, references by old keystext ids, Firebase ids reused (references preserved)
Settingssettings.lang (en, ru)user_settings.locale (en, ru)
Iconsmdi mdi-xxx formatmdi:xxx format

Import tooling

The importer lives at app/scripts/import-firebase.mjs. It reads a Firebase JSON export (the { accounts, categories, trns, settings, user } shape) and writes it into a Supabase project for one user.

Terminal
cd app
SUPABASE_URL=https://<ref>.supabase.co \
SUPABASE_SERVICE_ROLE_KEY=<service_role> \
node scripts/import-firebase.mjs <backup.json> --email <owner-email> --wipe
  • Owner - resolved from --email (defaults to the export's user.email) to an existing Supabase auth uid via the GoTrue admin API, or passed directly with --user-id. The user must have signed in at least once (e.g. via Google) so the imported rows' userId matches the uid they log in as.
  • Config - reads SUPABASE_URL + SUPABASE_SERVICE_ROLE_KEY from the env; with neither set it falls back to the local stack via supabase status. Writes go through PostgREST with the service_role key, so they bypass RLS and run over HTTPS (the direct Postgres connection's IPv6-only limitation does not apply).
  • --wipe deletes the user's existing trns / categories / wallets first; --dry-run prints the transformed counts and samples without writing.
  • Idempotent - rows upsert on their primary key and Firebase ids are reused verbatim (below), so re-running is safe.

Transforms it applies

  • Ids reused, not remapped - Supabase id columns are plain text with no FK constraints, so the script keeps the original Firebase keys as ids; parentId / walletId / categoryId references stay valid with no remap pass.
  • Accounts -> wallets; settings.lang -> user_settings.locale; settings.baseCurrency -> user_settings.baseCurrency.
  • Icons - mdi mdi-xxx -> mdi:xxx; empty -> mdi:category-outline.
  • Transfer / adjustment - categories named Transfer / Перевод / Adjustment are not real categories and are skipped; their transactions become categoryId = 'transfer' (type 2, with the expense* / income* fields) or categoryId = 'adjustment' (otherwise).
  • Legacy fields - walletFromId / walletToId -> expenseWalletId / incomeWalletId; amountFrom / amountTo -> expenseAmount / incomeAmount; description -> desc. Firebase-only fields with no column (category order, showStat, budgets, groups) are dropped.

No FK constraints exist in the schema, so import order does not matter. RLS policies and PowerSync sync rules ensure per-user isolation; on the owner's next login PowerSync syncs the imported rows to their device.

Current backend

The current backend is Supabase Postgres + PowerSync. See Architecture for a full description and Installation for local setup.