MainSDK
Search⌘K
How-to

Migrate from Firebase

Move Firestore, Firebase Auth and Storage calls onto MainSDK db, auth and files, with the codemod, the report and a pull request.

Planned for 0.7

Status Status is where the work is: Planned (in a milestone, not started), Building (tickets in progress), Preview (usable, may change), then the stability it ships at.

Stability The promise an interface ships with.

  • Stable Follows semantic versioning; breaking changes need a new major, 12 months of overlap and a codemod.
  • Beta Usable in production; may change before it becomes stable.
  • Experimental May change or go away; needs an explicit opt-in in main.yaml.
Stability levels
Updated 2026-10-28

Move Firestore, Firebase Auth and Storage calls onto MainSDK db, auth and files, with the codemod, the report and a pull request. Your Firebase account stays; nothing here deletes it.

Before you start:

  • A git repository with a clean working tree. mainsdk migrate works on a branch and never touches yours.
  • Node 22 and npx mainsdk. No main.yaml yet? migrate writes one; or start with Adopt an existing app.
  • Your Firebase keys stay where they are. Move them to secret: references when the driver is chosen (see Secrets).

1. Read the report

text
npx mainsdk migrate firebase --report-only

The report writes no file. It sorts every Firebase call in your code into three lists: mapped (an interface call replaces it), raw (it stays behind raw(), with the reason) and needs a person (a policy, a rule file or a call with no interface). Add --json for a machine-readable report.

For the sample app of this guide (packages/cli/test/fixtures/migrate/firebase), the report finds 4 mapped, 1 raw and 1 for a person.

2. What the codemod changes

WhereBeforeAfter
lib/auth.ts:5signInWithEmailAndPassword(auth, email, password)auth.signIn()
lib/orders.ts:4export const listOrders = () => getDocs(collection(db, 'orders'))db.orders.for(user).list()
lib/orders.ts:6export const addOrder = (row: unknown) => addDoc(collection(db, 'orderdb.orders.for(user).create()
lib/orders.ts:8export const removeOrder = (id: string) => deleteDoc(doc(db, 'orders',db.orders.for(user).remove(id)

Each rewrite is mechanical and one line at a time. Imports of Firebase go when no call needs them, and mainsdk is added to package.json.

3. What stays behind raw()

  • lib/live.ts:3: no realtime listener capability: onSnapshot

A call behind raw() keeps working through the Firebase host on your network allow list, and mainsdk check still sees it. Each one has a marker comment, // mainsdk migrate: ..., so you can find it later.

4. What needs a person

  • firestore.rules:1: security rules need a data label owner

The codemod leaves these lines alone and marks them. No tool should guess a policy.

5. Stateful moves are guided jobs

Data does not move with the code. Each stateful move is a job with steps and a rollback, listed in the pull request and never run by the codemod.

users: Move Firebase Authentication users

  1. Run firebase auth:export users.json (password hashes come with the project hash parameters).
  2. Import into the new auth driver on staging and compare counts.
  3. Switch auth.driver for 1% of sign-ins, then all.

Rollback: Set auth.driver back to firebase; Firebase keeps its users until the project is deleted.

documents: Copy Firestore collections to the app database

  1. Export each collection named in the report with gcloud firestore export.
  2. Load it into the new database and run the document-count check.
  3. Dual-write for one week, then read from the new database.

Rollback: Point db.driver back at Firestore; the dual-write kept it current.

6. Open the pull request

text
npx mainsdk migrate firebase

This records your sign-in and checkout flows as locked flows first, then creates the branch migrate/firebase, applies the codemods, adds .github/workflows/mainsdk-migrate.yml, runs mainsdk check and the locked flows, commits, pushes and opens one pull request with gh. If a gate fails, the branch is kept and no pull request opens (MAIN-MIGRATE-0001). --no-pr stops after the commit.

Locked flows run again in CI: npx mainsdk migrate flows.

Where you end

You end at the reference architecture mainsdk/saas-web: the same shape, with its tests, benchmark and chaos drills.

text
npx mainsdk init --template mainsdk/saas-web

Was this page helpful?

↑↓ moveEnter openEsc close

Not decided yet