Migrate from Firebase Auth

Move Firebase Authentication users into a SupaHost project with the admin API, keep their passwords using Firebase scrypt hashes, and handle Google and other social sign-ins.

You move Firebase Auth users into SupaHost by exporting them with the Firebase CLI and creating each one through the Supabase Auth admin API. Password users can keep their passwords, because Supabase Auth accepts Firebase's modified scrypt hashes. Social sign-in users simply sign in with the same provider again.

Plan

  • Decide how you'll map IDs. Supabase Auth user IDs are UUIDs and Firebase UIDs aren't, so each user gets a new ID. Keep the Firebase UID in user_metadata so you can rewrite ownership columns later (see Migrate from Firestore).
  • Set up the target project's Auth tab first: Site URL, redirect URLs, and any OAuth providers your users rely on.
  • If you also need to set "Skip email confirmation" or minimum password length, do it now in the Sign-in & security card.

Export from Firebase

firebase auth:export users.json --format=JSON --project recipe-box-prod

Then open the Firebase console → Authentication → Users → the menu above the user list → Password hash parameters. Copy base64_signer_key, base64_salt_separator, rounds and mem_cost.

Transform the hashes

Supabase Auth reads a Firebase scrypt hash in this layout (the password_hash field in supabase-js lists bcrypt, Firebase scrypt and argon2 as accepted formats):

$fbscrypt$v=1,n=<mem_cost>,r=<rounds>,p=1,ss=<base64_salt_separator>,sk=<base64_signer_key>$<salt>$<passwordHash>

<salt> and <passwordHash> come from each user in users.json. All values are standard base64; if your export uses URL-safe characters, swap - for + and _ for /.

Load the users

Run this once from your machine or a server, never in a browser:

import { createClient } from '@supabase/supabase-js'
import { readFileSync } from 'node:fs'

const admin = createClient(process.env.SUPABASE_URL!, process.env.SUPABASE_SERVICE_ROLE_KEY!, {
  auth: { persistSession: false, autoRefreshToken: false },
})
const { users } = JSON.parse(readFileSync('users.json', 'utf8'))
const hp = { n: 14, r: 8, ss: 'Bw==', sk: process.env.FIREBASE_SIGNER_KEY! } // from the console

for (const u of users) {
  if (!u.email) continue
  const hash = u.passwordHash
    ? `$fbscrypt$v=1,n=${hp.n},r=${hp.r},p=1,ss=${hp.ss},sk=${hp.sk}$${u.salt}$${u.passwordHash}`
    : undefined
  const { error } = await admin.auth.admin.createUser({
    email: u.email,
    email_confirm: u.emailVerified ?? false,
    ...(hash ? { password_hash: hash } : {}),
    user_metadata: { firebase_uid: u.localId, name: u.displayName },
  })
  if (error) console.error(u.email, error.message)
}

Users without a password (Google, Apple, GitHub and so on) are created with a random password. When they sign in with the same provider and a verified email that matches, Supabase Auth links the new identity to the existing user.

Test with two or three of your own accounts before running the full list. If a hash is rejected, or you'd rather not move hashes at all, create users without password_hash and ask them to reset their password (supabase.auth.resetPasswordForEmail). A middle path is to keep Firebase running for a few weeks and, on first sign-in, check the password against Firebase and then set it on SupaHost with auth.admin.updateUserById.

Switch the app

Replace Firebase calls with supabase-js: signInWithEmailAndPassword becomes supabase.auth.signInWithPassword, signInWithPopup becomes supabase.auth.signInWithOAuth, and onAuthStateChanged becomes supabase.auth.onAuthStateChange. Phone sign-in isn't available on SupaHost, so phone-only users need an email address.

Verify

select count(*), count(*) filter (where email_confirmed_at is not null) as confirmed
from auth.users;

Compare with the export, then sign in as a password user and a social user.

Updated 11 November 2026 · View as Markdown · Something wrong or missing? Tell us