← All playbooks
Authentication and identity migration

Firebase Authentication → Supabase

Export Firebase users with auth:export, copy the project's scrypt parameters, and create each person in Supabase Auth with a $fbscrypt$ hash. Sessions stay behind. The community SQL import does not bring the passwords with it.

Typical timelineA few days once the export is in hand. Longer if a store build still talks only to Firebase.A day of mapping for a directory you have already counted active work
Statusneeds review
Source profileFirebase auth:export and hash-parameter docs reviewed 2026-11-08
Destination profileSupabase Firebase Auth migration guide, admin createUser, and Auth password parser reviewed 2026-11-08
Documentation date
Review window
Evidence10 sources
This route needs review.Use it as a planning baseline, then verify the dated sources and test with representative data before production.
Before you migrate

Should you make this move?

Both platforms have a case. Compare what you gain with what you give up before scheduling the cutover.

Current platform

Firebase Authentication

Reasons to stay
  • Simple SDKs, familiar social providers, and close Firebase integration accelerate web and mobile sign-in
  • Centralized sign-in, federation, MFA, and user lifecycle controls reduce application security work
Reasons to leave
  • Authorization, enterprise identity, tenant design, and advanced migration controls are less comprehensive
  • Passwords, sessions, factors, identities, and authorization policy are unusually sensitive to migration errors
New platform

Supabase

What gets better
  • PostgreSQL, authentication, storage, realtime, and edge functions offer an open backend stack
  • Managed infrastructure removes a large amount of operational work
What gets worse
  • Production scaling and service integration still demand database and infrastructure judgment
  • Platform-specific services can create architectural lock-in
Best of the move

Supabase: PostgreSQL, authentication, storage, realtime, and edge functions offer an open backend stack. This removes a major source-side concern: Authorization, enterprise identity, tenant design, and advanced migration controls are less comprehensive.

Worst of the move

What you lose: Simple SDKs, familiar social providers, and close Firebase integration accelerate web and mobile sign-in. What you inherit: Production scaling and service integration still demand database and infrastructure judgment.

Jump to a section
01At a glance

Know the shape of the move.

Transfer outcome12 features audited
Transfer outcome distributionClean transfer: 0, Partial transfer: 4, Manual rebuild: 5, Not transferred: 3.
Clean0
Partial4
Manual5
Lost3
Mapping route7 of 10 fields have a destination path

This timeline assumes

  • You can run firebase auth:export as a project Editor or Owner. The file-format table says the password hash and salt require that role.
  • You can open Authentication, Users, the three-dot menu, and Password hash parameters, then store base64_signer_key, base64_salt_separator, rounds, and mem_cost outside the repo.
  • Firebase Auth stays up through the rollback window. You can still sign a test user in there.
  • Firestore, Storage, and Cloud Functions are a different move. That one is Firebase → Supabase. This route is the directory.
  • The site already documents Firebase Authentication → Clerk and Firebase Authentication → Auth0, if you want a hosted login product instead of Supabase Auth.
02What transfers

What survives the move.

“Partial” and “manual” are not footnotes. They are work that must be scheduled and verified.

ItemOutcomeImpactWhat happensMitigation
Firebase scrypt passwordspartialcriticalauth:export writes passwordHash and salt only for passwords hashed with Firebase's modified scrypt. Supabase Auth accepts that hash on createUser as password_hash, in the $fbscrypt$ form its parser expects. New passwords Supabase creates itself are bcrypt.Copy the four hash parameters from the console before you import. Sign in as a user whose password you know after the row lands.
Any other password hashmanualcriticalFirebase's export docs say other algorithms leave passwordHash and salt empty. Those accounts were usually imported from somewhere else, and Firebase only re-hashes them with scrypt on the next sign-in.List every empty hash. Send a reset, or keep Firebase as the checker until that person has signed in once.
The community SQL importlostcriticalimport_users.js inserts an empty encrypted_password and stores the whole user object in raw_user_meta_data. Its provider switch only names Google and Facebook. GitHub and the rest are written down as email. The password middleware in that repo is still marked in progress.Use the admin API for the real import. Treat a successful SQL load as a directory sketch, then check that a known password works.
Firebase uid as the primary keypartialcriticalSupabase user ids are UUID v4. createUser can take an id only in that form. A Firebase localId is not one. Tom at Minimum Code got ID must conform to the uuid v4 format when he tried to keep the old id.Let Supabase assign the id. Store localId on app_metadata, and map it before you move any row that points at the old uid. That data move is Firebase → Supabase.
Sessions and refresh tokenslosthighA Firebase refresh token is not a Supabase session. People sign in again, unless you keep Firebase Auth as a third-party provider and pass its JWT through as the Supabase access token.Ship the client that can verify a Supabase session before you disable a provider. Or register Firebase as a third-party auth integration and leave sign-in where it is for now.
Social loginspartialcriticalThe export's providerUserInfo lists each linked provider and its rawId. The documented providerId values include google.com, facebook.com, github.com, and twitter.com. Automatic linking attaches a new OAuth identity when the email matches an existing confirmed user. SAML users are not linking targets.Copy the OAuth client id and secret into the Supabase provider. Confirm the imported email first. Sign in once with each provider you still offer.
Two Firebase accounts, one emailmanualhighSupabase keeps one user per email so automatic linking can find them. Firebase can hold separate accounts on that address, and linking those was one of the pains Analyst Builder described.Pick the surviving localId before createUser. Write down the one you skip.
Custom claims and rolesmanualhighuser_metadata is editable by the signed-in user, and it is copied into the access token. app_metadata is service-role only, and it is in the token too. Tom's test user set role admin on his own user_metadata after the community import put the Firebase record there.Put claims you do not want edited into app_metadata. Do not put the hash string or the signer key in either object.
Phone numberspartialhighThe export includes phoneNumber. createUser can take phone and phone_confirm. The community SQL import writes phone as null.Pass the phone on createUser and set phone_confirm when Firebase already verified it. Send one test code.
Anonymous userslosthighAn anonymous Firebase user has no email, no phone, and no password. createUser needs an email or a phone, so that session does not become a Supabase user.Count them. Keep Firebase Auth in front, via the third-party integration, until those sessions can die, or accept that they start over.
MFA factorsmanualhighThe documented auth:export fields do not include a TOTP secret. There is no hash-style import for an enrolled factor.Have one enrolled user add a factor on Supabase before you tell the list. Keep the Firebase check until that path works.
Templates, blocking functions, and tenantsmanualhighAction URLs, email templates, and blocking functions do not come across in the user file. Identity Platform tenants are separate user pools. The documented auth:export command takes a file and a format, and it has no tenant flag.List tenants in the console. If you have any, export them with the Admin SDK for that tenant id. auth:export alone is the default pool. Rebuild each template and hook, and send one mail from Supabase before cutover.
03Field and feature mapping

Where each thing goes.

SourceDestinationMethodNotes
localIdapp_metadata.firebase_uidmanualSupabase assigns a UUID. Keep the Firebase id beside it.
passwordHash and salt, Firebase scryptauth.users.encrypted_password via password_hashtransformBuild $fbscrypt$v=1,n=<mem_cost>,r=<rounds>,p=1,ss=<salt separator>,sk=<signer key>$<salt>$<passwordHash>.
Empty passwordHashNo destinationunsupportedReset, or check the password against Firebase on the next login and then set it.
email and emailVerifiedemail and email_confirmtransformSet email_confirm when Firebase already verified the mailbox. Linking looks for a confirmed email.
phoneNumberphone and phone_confirmtransformThe community SQL import leaves phone null. Pass it yourself.
displayName and photoUrluser_metadatatransformThe person can change this later. Keep it small.
providerUserInfo rawIdauth.identities after sign-in or linkIdentityautomaticSame OAuth client id avoids a fresh consent screen. Test one linked account.
Custom claimsapp_metadatamanualNot a documented column in the export file. If your JSON has them, they do not belong in user_metadata.
Anonymous userNo destinationunsupportedNo email and no phone, so there is nothing for createUser to keep.
Sessions and refresh tokensNo destinationunsupportedPeople sign in again, or you keep passing the Firebase JWT.
04Before you begin

Make the move recoverable.

Backup procedure

Create the source-of-truth backup

Keep an export you can re-read, and keep the hash parameters off the app repo.

  1. Count users in the Firebase console, split by provider.
  2. Download the password hash parameters and store them with the same care as a private key.
  3. Run firebase auth:export users.json --format=json from an Editor or Owner account. The .json suffix selects the format.
  4. If the project uses Identity Platform tenants, list them in the console. auth:export has no tenant flag, so a tenant export is a separate Admin SDK call. Label each file with the tenant id.
  5. Store the JSON outside the repo, with the counts and the account that ran the export.

Proof to capture: The console count, the export file, and a note that passwordHash is present for the scrypt users you expect.

Transformation · auth.admin.createUser

Build the hash string from the export and the console parameters

createUser will only check a Firebase password if the hash uses the $fbscrypt$ form.

  1. Read mem_cost, rounds, base64_salt_separator, and base64_signer_key from the Password hash parameters dialog. Firebase's sample config shows SCRYPT, and most projects use that modified algorithm.
  2. For each user with both passwordHash and salt, build $fbscrypt$v=1,n=<mem_cost>,r=<rounds>,p=1,ss=<salt separator>,sk=<signer key>$<salt>$<passwordHash>. The Auth parser treats n as that mem_cost value and r as rounds. p has to be at least 1. Use 1 for Firebase's own scrypt.
  3. Call createUser with the service role, on a machine that is not the browser. Pass email, password_hash, and email_confirm true when emailVerified is true. Pass phone and phone_confirm the same way. Put localId in app_metadata.
  4. Do not also pass password. Do not put the hash or the signer key into user_metadata or app_metadata. Both objects are copied into the access token.
  5. Skip rows with an empty passwordHash. They go on the reset list, or through a Firebase sign-in check on the next attempt.

Proof to capture: Save the input, output, command or tool settings, warnings, and final item counts.

Transformation · Third-party Auth integration

Keep Firebase as the login only if you are not ready to move it

Supabase can trust a Firebase JWT while the directory still lives in Firebase.

  1. In the Supabase dashboard, add a Firebase third-party auth integration with the Firebase project id.
  2. Give each Firebase user a custom claim role set to authenticated. Without it, Supabase treats the JWT as anon.
  3. Pass the Firebase ID token from accessToken on the Supabase client. Call getIdToken(true) after a blocking function sets that claim, because onCreate is not synchronous.
  4. On the hosted platform, Supabase rejects JWTs from Firebase project ids you did not register. Firebase uses one set of signing keys for every project. If you self-host, add a restrictive RLS policy that checks the issuer, or an unrelated Firebase project can reach your tables.
  5. This keeps the current sessions. It does not move the users. Come back to createUser when you want Supabase to be the password store.

Proof to capture: Save the input, output, command or tool settings, warnings, and final item counts.

05Handle with care

The things most likely to hurt.

These are operating limits. Treat every “Stop if” condition as a blocked migration, not a suggestion.

Import

The SQL import looks finished and nobody can sign in

criticallikely likelihood

import_users.js writes an empty encrypted_password. Tom ran it on three emulator users and the old password returned Invalid login credentials.

Consequence
You cut over onto a directory that has emails and no passwords. Analyst Builder hit this class of gap in production and asked all 7,000 beta users to reset.
Mitigation
After the load, sign in as the test user. If the hash path is not ready, send the reset while Firebase can still sign people in.

Stop if: A known password fails and the reset mail is not ready.

Import

The signer key rides along in the token

criticalpossible likelihood

The community script JSON.stringifies the user into raw_user_meta_data. Tom's test user then edited that object with his own token and granted himself role admin.

Consequence
A password hash, a salt, or a project signer key can end up in every access token, and the person can change the claims you stored there.
Mitigation
Strip the export down to name and photo for user_metadata. Put firebase_uid and roles in app_metadata only.

Stop if: A decoded access token contains passwordHash, salt, or base64_signer_key.

Pilot

The rehearsal used the emulator

highlikely likelihood

Tom says the emulator stores fake hashes. He had to use the sample vector from Firebase's scrypt repository to get a real check.

Consequence
The pilot passes on a fake hash, so your project's signer key, rounds, and mem_cost never get checked.
Mitigation
Pilot against a copy of the production export, or against that published vector plus your own parameters on a staging project.

Stop if: The only successful password check used an emulator hash.

Sessions

A wrong clock looks like a failed migration

mediumpossible likelihood

After Analyst Builder moved, some customers were logged out immediately. Kasun traced it to the machine clock GoTrue uses when it creates the token.

Consequence
Support treats a client clock as a bad import, and people who just reset a password think the reset failed.
Mitigation
Sign in from a device with a correct clock, and from one you have set a few minutes off, before you call the cutover done.

Stop if: A test device with a correct clock is signed out and the password error does not explain it.

06Precise timeline

Do the work in this order.

Estimate forOne Firebase project, email and password plus at least one social provider, Supabase Auth already created
Total elapsedA few days once the export is in hand. Longer if a store build still talks only to Firebase.
Active workA day of mapping for a directory you have already counted
BufferAnalyst Builder kept both systems up for a 7,000-user beta and sent a reset instead of moving hashes. Tom's three-user hash check was one sitting, and it was a lab, not a client app.
01
Day 1Count providers and copy the hash parameters1 day
02
Days 1 to 2Export and separate the empty hashes1 to 2 days
03
Day 2 or 3Create a handful of users1 day
04
After the pilotShip a client that can take a Supabase sessionA few days, longer if a store reviews the build
05
Cutover dayImport the rest and turn providers over1 day
  1. Day 1 · inventory

    Count providers and copy the hash parameters

    2 to 4 hours active1 day elapsedNone waiting
    • Count email, phone, social, and anonymous users.
    • Save the four hash parameters outside the repo.
    • List OAuth client ids you will reuse.
    • List tenants, blocking functions, and templates you will rebuild. A tenant is not in the auth:export file.
    Stop / go checkpoint

    Can this project export hashes?

    Go when: An Editor or Owner can run the export, and the hash parameters are stored.

    Stop when: The account that can export is not available, or you cannot tell which tenant is production.

  2. Days 1 to 2 · export

    Export and separate the empty hashes

    Half a day active1 to 2 days elapsedThe export command waiting
    • Run firebase auth:export users.json --format=json.
    • Count rows with a passwordHash, rows without one, and duplicate emails.
    • Pick the surviving account where two localIds share an email.

    Depends on: inventory

    Stop / go checkpoint

    Is the file the directory you think it is?

    Go when: The count matches the console, and every duplicate email has a survivor.

    Stop when: Every passwordHash is empty and the role you used was not Editor or Owner.

  3. Day 2 or 3 · pilot

    Create a handful of users

    Half a day active1 day elapsedNone, unless you are waiting on a staging project waiting
    • createUser for one scrypt user, one social user, one phone user, and one duplicate-email pair.
    • Sign in with the known password.
    • Decode the access token and confirm it has no hash and no signer key.

    Depends on: export

    Stop / go checkpoint

    Does the hash check work on this project's parameters?

    Go when: The known password returns a session, and a wrong password does not.

    Stop when: The only passing check used an emulator hash, or the token contains the signer key.

  4. After the pilot · clients

    Ship a client that can take a Supabase session

    A few days activeA few days, longer if a store reviews the build elapsedApp review, if you ship a native app waiting
    • Verify a Supabase access token on the API.
    • Keep the Firebase path until that build is what people are running.
    • If you are not moving passwords yet, wire the third-party Firebase JWT instead and stop here.

    Depends on: pilot

    Stop / go checkpoint

    Can a production build sign in with Supabase?

    Go when: The build people already have accepts a Supabase session, or it still sends a Firebase JWT on purpose.

    Stop when: Only a local build can reach Supabase.

  5. Cutover day · backfill

    Import the rest and turn providers over

    A day active1 day elapsedNone if the export is fresh waiting
    • Export once more and import anyone created after the pilot.
    • createUser for the remaining scrypt users.
    • Point each social provider at Supabase. Leave Firebase up for the empty-hash list.

    Depends on: clients

    Stop / go checkpoint

    Is every password accounted for?

    Go when: Every scrypt user can sign in, or is named on the reset list.

    Stop when: The reset mail is not ready and the empty-hash list is not empty.

07The point of change

Cut over with a way back.

Go live

Cutover

New sign-ins go to Supabase, and Firebase can still check a password that had no hash.

Recommended window: After the known-password check has passed on the production export, and the client people are running can take a Supabase session.

  1. Stop new Firebase signups. Write down the time.
  2. Export once more and create anyone who appeared after the last file.
  3. Turn each social provider to Supabase. Keep Firebase able to check a password for the empty-hash list.
  4. Send the reset only to that list. Do not send it to people whose $fbscrypt$ login already worked.
  5. Leave the Firebase project billed. Do not delete the users this week.

Proof to capture: The signup freeze time, a scrypt login, one social login, and the reset list with a count.

Return to safety

Rollback

Send sign-in back to Firebase without throwing away the Supabase rows you may need to diff.

Deadline: While Firebase Auth still accepts a sign-in and the client can still verify a Firebase JWT.

  1. Point the login path back at Firebase.
  2. Leave the Supabase users in place.
  3. If someone signed up only on Supabase during the overlap, write that id down before you turn the new signup off.
  4. Read one Firebase login before you call the rollback done.

Proof to capture: A production login through Firebase, and a list of accounts that exist only in Supabase.

Rollback immediately when
  • The known-password test user cannot sign in on Supabase.
  • A social login creates a second app profile.
  • A decoded access token contains the signer key or a password hash.
  • The installed client cannot verify a Supabase session, and you have already disabled Firebase sign-in.
08Verification report

Prove the migration worked.

Every blocking check must pass. Capture the evidence before cleanup begins.

0%
Interactive report preview0 / 9 checks passed
PassIDCheckMethodExpected resultEvidence
USR-01BlockingCounts by providerCompare the Firebase console and the export with auth.users, split by password, phone, social, and anonymous.Counts match, or the exception list names every skip.The count table.
PWD-01BlockingA known scrypt password still worksSign in on Supabase as the test user whose password you set in Firebase.The session is issued and the app loads the old profile.The user id and the time of the login.
HSH-01BlockingEmpty hashes are listedDiff the export for rows with no passwordHash.Each one is on the reset list or still checked by Firebase. None are silent.The diff and the reset list.
UID-01BlockinglocalId maps to the new UUIDOpen app_metadata.firebase_uid for the pilot users and join it to your app tables.Every app row you kept points at the new id, through that map.The map sample.
SOC-01BlockingEach provider hits the old profileSign in with each social provider you still offer.The app user is the imported one, not a second profile.The provider id and the Supabase user id.
DUP-01BlockingShared emails have a decisionOpen the duplicate-email list and the Supabase user each one became.Each pair has a surviving account.The decision list.
TOK-01BlockingThe access token is safe to logDecode a session token for an imported user.No passwordHash, salt, or signer key. Roles you care about sit in app_metadata.The decoded claim names, not the token itself.
PHN-01BlockingA verified phone stays verifiedSign in as a user Firebase already verified by phone.Supabase does not ask them to verify that number again.The user id. Skip only if the project has no phone users, and write that down.
APP-01BlockingThe API accepts the new tokenCall a signed-in route with a Supabase access token for a migrated user.The route returns the same app profile a Firebase token returned.The response and the user id.
09Post-migration cleanup

Remove the scaffolding safely.

Safe after: A week of Supabase sign-ins, with the empty-hash list either mailed or still checked by Firebase on purpose.

  1. Delete the export and the hash-parameter file from laptops and CI once you are sure you will not rerun the import.
  2. Revoke the service-role key you used for createUser, and the Firebase private key if you generated one for the community script.
  3. Remove the Firebase JWT check only after the rollback window, and only if you are not keeping the third-party integration.
  4. Keep the Firebase project until finance has the last invoice and the reset list is done.
  5. Set the next review date. The data move, if you still have one, stays on Firebase → Supabase.
10Experiences from the field

When the plan met reality.

First-hand accounts are preferred. Vendor case studies are labeled, and every note below is an editorial paraphrase—follow the link for the full context.

First-person accountKasun Vithanage · Slides.com

Analyst Builder's Journey from Firebase to Supabase2024

Kasun Vithanage presented Analyst Builder's move at a Supabase meetup in August 2024. The product was a closed beta of 7,000 users. They adapted a script from the Supabase team, imported the users with SQL, filled a user_providers table, and dropped the existing passwords. Beta users were asked to reset. A custom x-auth-provider header let the app talk to Firebase and Supabase during the overlap. He describes GitHub login failing for some people on Firebase, painful identity linking, and no auth audit logs. After the move he says 99% of login-related issues were gone and LinkedIn sign-in was available. Some customers were then logged out immediately. He traced that to the machine clock GoTrue uses when it creates a token, and fixing the clock fixed the logout. A later slide says they reached 50,000+ with Supabase. That figure is not a count of imported accounts.

What they recommend
  • If the import leaves passwords behind, send the reset while Firebase can still sign people in, and keep a flag that can accept the old token.
  • Test a same-email account before you promise linking. He called Firebase's linking painful, and the new side only worked once each person was one user.
Worth noticing
  • A SQL import of the directory was not a password import. They chose a reset for the whole beta of 7,000.
  • The failure he still hit was clocks. A wrong client clock looked like an instant logout after an otherwise successful move.
First-person accountTom · Minimum Code

Firebase to Supabase migration, passwords included2026

Tom, founder of Minimum Code, writes that this page is not a client migration. On 6 October 2026 he built a three-user project tracker in the Firebase emulators and moved it to local Supabase Auth v2.197.0. The community import_users script inserted all three with an empty password, so the old password returned Invalid login credentials. He then created a user through the admin API with a $fbscrypt$ hash, using the sample vector from Firebase's scrypt repository because the emulator stores fake hashes. The right password returned HTTP 200 and a session. A wrong one was refused. After that login the stored hash was still the Firebase string. Setting the Firebase uid as the Supabase id failed with ID must conform to the uuid v4 format. The community import had also copied the user record into user_metadata, and a normal user could change it, including giving himself role admin.

What they recommend
  • Rehearse the hash import with a production export or Firebase's published test vector. An emulator export does not contain a real scrypt hash.
  • Keep the signer key and the hash out of user_metadata. In his run a signed-in user edited that object, including a role.
Worth noticing
  • The community SQL import he ran does not fill encrypted_password. The admin password_hash path did, on local Auth 2.197.0, and that login did not rewrite the hash to bcrypt.
  • The community scripts crashed on Node 25. Node 22 ran them. npm ci failed on the repo lockfile, and npm install worked.
Sources and evidence

Verify against the primary material.

Platform behavior changes. Check these sources and the review dates above before executing a production migration.

  1. Supabase: migrate from Firebase AuthAccessed 2026-11-08
  2. supabase-community/firebase-to-supabase auth READMEAccessed 2026-11-08
  3. supabase-community/firebase-to-supabase import_users.jsAccessed 2026-11-08
  4. Firebase: auth:import and auth:exportAccessed 2026-11-08
  5. Supabase Auth: AdminUserAttributes.password_hashAccessed 2026-11-08
  6. Supabase Auth: Firebase scrypt parserAccessed 2026-11-08
  7. Supabase: Firebase Auth as a third-party providerAccessed 2026-11-08
  8. Supabase: identity linkingAccessed 2026-11-08
  9. Kasun Vithanage: Analyst Builder's journey from Firebase to SupabaseAccessed 2026-11-08
  10. Tom at Minimum Code: Firebase to Supabase, passwords includedAccessed 2026-11-08