· supabase / firebase / migration

Cách migrate Firebase sang Supabase: Hướng dẫn từng bước

Migrate Firebase sang Supabase mà không mất Auth, Firestore hay Storage — thứ tự đúng, 8 điểm chặn cần biết, và khoảng trống Analytics cần lên kế hoạch trước.

Bởi · Cập nhật 20 tháng 5, 2026

2.348 từ · 12 phút đọc

Bạn có thể migrate Firebase sang Supabase mà không mất auth session, dữ liệu Firestore, hay file trên Storage — nhưng chỉ khi thực hiện đúng thứ tự và biết rõ phần nào không có tương đương. Bài này hướng dẫn migrate Auth, Firestore, và Storage bằng bộ công cụ firebase-to-supabase (153 ⭐, cập nhật 2026-05-15), kèm code so sánh trước/sau cho cả bốn dịch vụ và script Firestore migration đầy đủ.

Một khoảng trống không thể lấp được: Firebase Analytics không có tương đương trong Supabase. Nếu bạn phụ thuộc vào Firebase Analytics, hãy migrate dữ liệu đó sang dịch vụ analytics riêng trước khi động vào bất cứ thứ gì khác.

Bài này dành cho ai

Developer đang dùng Firebase Blaze plan và muốn chuyển sang backend ưu tiên SQL, có thể self-host. Nếu bạn đang dùng Firebase Spark (miễn phí) và chưa gặp giới hạn nào, migration này tốn thời gian thật sự mà lợi ích ngắn hạn chưa rõ — hãy quay lại khi hóa đơn bắt đầu đau.

So sánh dịch vụ Firebase và Supabase

Dịch vụ FirebaseTương đương SupabaseĐộ phức tạp
Firebase AuthSupabase AuthTrung bình — hash Scrypt được giữ, nhưng định dạng UUID thay đổi
FirestorePostgresCao — collection dạng document phải thiết kế lại thành bảng quan hệ
Realtime DatabaseSupabase RealtimeTrung bình — mô hình pub/sub tương tự; giới hạn kết nối khác nhau
Cloud StorageSupabase StorageThấp — script download/upload xử lý được
Cloud FunctionsEdge FunctionsTrung bình — khác runtime; hầu hết function chuyển được
Firebase Analytics❌ Không có tương đươngLên kế hoạch migrate sang Mixpanel, PostHog, hoặc tương tự

Nếu bạn vẫn đang cân nhắc có nên migrate không, hãy xem Supabase vs Firebase (2026) để so sánh chi tiết về chi phí và tính năng.

Trước khi migrate: thiết kế schema

Phần khó nhất của quá trình migrate là Firestore, không phải Auth hay Storage. Collection dạng document với cấu trúc lồng tùy ý không ánh xạ gọn gàng vào bảng Postgres. Schema phải được thiết kế trước khi viết bất kỳ migration script nào. Thiết kế sai đồng nghĩa với chạy lại toàn bộ quá trình.

Với mỗi Firestore collection:

  1. Liệt kê mọi field và kiểu dữ liệu của nó.
  2. Xác định các object và array lồng nhau. Quyết định: tạo bảng riêng với foreign key, hay dùng cột JSONB?
  3. Quyết định chiến lược primary key cho Postgres. Document ID của Firebase là string tùy ý. Supabase mặc định dùng UUID. Bạn có thể giữ string ID trong cột firebase_id để tra cứu trong giai đoạn chuyển tiếp, nhưng code ứng dụng cuối cùng vẫn phải chuyển sang tra cứu theo UUID.

Ghi lại các quyết định này. Bạn sẽ cần khi chạy firestore2json.js. Khi Firestore đã vào Postgres, bạn cũng sẽ cần một query layer — so sánh TypeScript ORM của chúng tôi đối chiếu Drizzle, Prisma, và Kysely để bạn chọn trước khi viết query đầu tiên.

Migrate Firebase Auth sang Supabase

Quá trình migrate Auth của Supabase giữ nguyên hash mật khẩu Scrypt, nên người dùng hiện tại không cần đặt lại mật khẩu. Hướng dẫn chính thức đi sâu hơn vào từng bước.

Bước 1: Export danh sách user Firebase

firebase auth:export users.json --format=json

Lệnh này ghi toàn bộ bản ghi người dùng — bao gồm các tham số hash Scrypt — vào users.json. Với project có hơn 1 triệu người dùng, thêm --limit và lặp theo từng batch.

Bước 2: Transform và import bằng bộ công cụ

npx github:supabase-community/firebase-to-supabase/auth

Bộ công cụ đọc users.json, ánh xạ Firebase UID sang UUID (duy trì một bảng tra cứu bạn sẽ cần sau cho Firestore), và gọi Supabase Admin API để bulk-insert người dùng. Các tham số hash Scrypt được truyền qua cột raw_app_meta_data, nên việc xác thực vẫn hoạt động với người dùng chưa đăng nhập sau khi migrate.

Lỗi thường gặp: Nếu email của người dùng đã tồn tại trong Supabase (từ lần import một phần trước đó), quá trình import sẽ bỏ qua dòng đó mà không báo lỗi rõ ràng. Kiểm tra số lượng skipped trong log output so với tổng số user Firebase trước khi tiếp tục.

Bước 3: Cấu hình lại Google Sign-In

Google OAuth cần thiết lập lại trong Supabase dashboard tại Authentication → Providers → Google. Client ID và secret từ Firebase project có thể tái sử dụng — chúng gắn với Google Cloud project của bạn, không phải với Firebase.

Migrate dữ liệu Firestore sang Postgres

Bộ công cụ firebase-to-supabase cung cấp một pipeline gồm ba script để migrate Firestore. Cài một lần, chạy theo thứ tự.

git clone https://github.com/supabase-community/firebase-to-supabase
cd firebase-to-supabase
npm install

Giai đoạn 1: Liệt kê các collection

node firestore/collections.js

Kết quả là danh sách tên các collection ở cấp cao nhất. Kiểm tra kỹ — các sub-collection lồng trong document không xuất hiện ở đây và cần xử lý riêng.

Giai đoạn 2: Export Firestore ra JSON

node firestore/firestore2json.js <collection-name> <output-file.json>

Chạy một lần cho mỗi collection. Với collection lớn, thêm --limit và phân trang. Kết quả bao gồm document ID của Firestore dưới dạng trường _id.

Giai đoạn 3: Import JSON vào Postgres

node firestore/json2supabase.js <output-file.json> \
  --supabaseUrl=<your-project-url> \
  --supabaseKey=<service-role-key> \
  --tableName=<target-table>

Công cụ tạo bảng đích nếu chưa có, suy luận kiểu cột từ JSON. Với các object lồng nhau, mặc định là JSONB. Bạn có thể ghi đè bằng cờ --schema trỏ đến file JSON schema.

Script migrate đầy đủ cho collection users:

import { createClient } from '@supabase/supabase-js';
import * as admin from 'firebase-admin';
import * as fs from 'fs';

const supabase = createClient(
  process.env.SUPABASE_URL!,
  process.env.SUPABASE_SERVICE_ROLE_KEY!
);

admin.initializeApp({
  credential: admin.credential.applicationDefault(),
});

const db = admin.firestore();

async function migrateCollection(collectionName: string) {
  const snapshot = await db.collection(collectionName).get();
  const rows = snapshot.docs.map((doc) => ({
    id: crypto.randomUUID(), // UUID mới cho Supabase
    firebase_id: doc.id,    // giữ lại ID gốc để tra cứu
    ...doc.data(),
    created_at: doc.data().createdAt?.toDate?.() ?? new Date(),
  }));

  const { error } = await supabase.from(collectionName).insert(rows);

  if (error) {
    console.error(`Failed on ${collectionName}:`, error.message);
    process.exit(1);
  }

  console.log(`Migrated ${rows.length} records from ${collectionName}`);
}

await migrateCollection('users');
await migrateCollection('posts');

Lỗi thường gặp: Firestore Timestamp được serialize thành object { _seconds, _nanoseconds }, không phải chuỗi ISO. Script trên gọi .toDate() một cách tường minh. Bỏ qua bước này, bạn sẽ nhận được JSONB blob thay vì cột timestamptz như mong muốn.

Migrate Firebase Storage sang Supabase Storage

Migrate Storage là phần ít tốn công nhất. Bộ công cụ cung cấp hai script: một để tải file từ Firebase Storage về máy, một để upload lên Supabase bucket.

Bước 1: Tải file từ Firebase Storage

node storage/download.js \
  --firebaseBucket=<your-project>.appspot.com \
  --localPath=./storage-backup

Với bucket lớn, quá trình này có thể mất nhiều thời gian. Cấu trúc thư mục local phản ánh cấu trúc thư mục remote.

Bước 2: Upload lên Supabase Storage

node storage/upload.js \
  --supabaseUrl=<your-project-url> \
  --supabaseKey=<service-role-key> \
  --bucketName=<bucket> \
  --localPath=./storage-backup

Cài đặt public/private của bucket độc lập với access rule của Firebase — thiết lập trong Supabase dashboard và cập nhật RLS policies tương ứng.

Lỗi thường gặp: Đường dẫn Firebase Storage bắt đầu bằng dấu chấm (.) hoặc chứa dấu gạch chéo liên tiếp (//) có thể gây lỗi khi upload lên Supabase. Kiểm tra trước bằng find ./storage-backup -name ".*" trước khi upload.

Cập nhật client SDK

Sau khi dữ liệu đã migrate xong, cập nhật code ứng dụng. Dưới đây là code so sánh trước/sau cho cả bốn dịch vụ.

Auth: đăng nhập

// Firebase
import { getAuth, signInWithEmailAndPassword } from 'firebase/auth';
const auth = getAuth();
const { user } = await signInWithEmailAndPassword(auth, email, password);

// Supabase
import { createClient } from '@supabase/supabase-js';
const supabase = createClient(url, anonKey);
const { data, error } = await supabase.auth.signInWithPassword({ email, password });
const user = data.user;

Database: đọc và ghi

// Firebase — đọc
import { getFirestore, doc, getDoc } from 'firebase/firestore';
const db = getFirestore();
const snap = await getDoc(doc(db, 'posts', postId));
const post = snap.data();

// Supabase — đọc
const { data: post } = await supabase
  .from('posts')
  .select('*')
  .eq('id', postId)
  .single();

// Firebase — ghi
import { setDoc } from 'firebase/firestore';
await setDoc(doc(db, 'posts', postId), { title, body });

// Supabase — ghi
await supabase.from('posts').upsert({ id: postId, title, body });

Storage: upload

// Firebase
import { getStorage, ref, uploadBytes } from 'firebase/storage';
const storage = getStorage();
await uploadBytes(ref(storage, `uploads/${filename}`), file);

// Supabase
await supabase.storage
  .from('uploads')
  .upload(filename, file, { upsert: true });

Realtime: subscribe

// Firebase
import { getDatabase, ref, onValue } from 'firebase/database';
const db = getDatabase();
onValue(ref(db, `rooms/${roomId}`), (snap) => {
  console.log(snap.val());
});

// Supabase
supabase
  .channel(`room:${roomId}`)
  .on('postgres_changes', { event: '*', schema: 'public', table: 'rooms', filter: `id=eq.${roomId}` }, (payload) => {
    console.log(payload.new);
  })
  .subscribe();

Lưu ý sự khác biệt về mô hình: Supabase Realtime subscribe vào các thay đổi row của Postgres, không phải các JSON path tùy ý. Điều này có nghĩa là subscription của bạn luôn gắn với một bảng cụ thể và có thể lọc bằng điều kiện Postgres — có cấu trúc hơn so với Firebase Realtime Database, vốn cho phép subscribe vào bất kỳ path nào.

So sánh giá: Supabase có tiết kiệm hơn không?

Firebase BlazeSupabase Pro
Giá cơ bản$0 (pay-as-you-go)$25/tháng
AuthMiễn phí đến 50K MAU100K MAU, sau đó $0.00325/MAU
Database$0.06/GB/tháng (Firestore)8 GB, sau đó $0.125/GB
Storage$0.026/GB/tháng100 GB, sau đó $0.021/GB
Bandwidth$0.12/GB250 GB, sau đó $0.09/GB
Kết nối RealtimeSpark: 100; Blaze: 200K/database500 đồng thời trên Pro

Với khoảng 30.000–50.000 monthly active user, Supabase Pro thường rẻ hơn Firebase Blaze về tổng chi phí hàng tháng. Dưới ngưỡng đó, giá pay-as-you-go của Firebase thường có lợi hơn. Trên 500 kết nối Realtime đồng thời, bạn cần plan Team hoặc Enterprise của Supabase.

Supabase Pro còn bao gồm daily backup, branching, và custom domain — những tính năng phải trả thêm hoặc cần giải pháp thay thế trên Firebase.

8 điểm chặn cần biết trước khi bắt đầu

1. Mismatch UUID trong Auth. Firebase UID là string dạng abc123. Supabase UID là UUID. Mọi bảng có foreign key user_id cần được cập nhật sau khi migrate auth. Bộ công cụ tạo bảng mapping firebase_uid_to_supabase_uid để hỗ trợ — dùng nó trước khi xóa các cột firebase_id.

2. Định dạng Timestamp. Firestore Timestamp serialize thành object { _seconds, _nanoseconds } khi export JSON. Gọi .toDate() trước khi insert vào Postgres. Bỏ qua bước này, bạn sẽ insert JSONB blob vào cột timestamptz — Postgres không báo lỗi khi insert, nhưng query sẽ thất bại.

3. Sub-collection lồng nhau. Firestore document có thể chứa sub-collection không xuất hiện trong export cấp cao nhất. Chạy collections.js và tìm sub-collection thủ công. Làm phẳng chúng vào Postgres đòi hỏi thiết kế join table trước khi migrate, không phải sau.

4. Giới hạn kết nối Realtime. Firebase Realtime Database trên Blaze giới hạn 200K kết nối đồng thời mỗi database. Supabase Pro giới hạn 500 kết nối đồng thời. Nếu peak của bạn vượt ngưỡng này, nâng cấp lên plan Team trước khi go live, hoặc triển khai connection pooling phía client.

5. Không có tương đương Firebase Analytics. Đây là khoảng trống khó bù đắp. Lên kế hoạch migrate analytics sang dịch vụ bên thứ ba (Mixpanel, PostHog, Amplitude) như một project riêng. Migrate dữ liệu Firebase Analytics trước; đừng để nó trở thành điểm chặn cho phần còn lại.

6. Google Sign-In cần thiết lập lại. OAuth client ID và secret vẫn dùng được, nhưng bạn phải cấu hình lại Authorized redirect URI trong Google Cloud Console để trỏ đến Auth callback URL của Supabase project. Người dùng Google Sign-In hiện tại sẽ thấy màn hình đồng ý lại nếu redirect URI thay đổi.

7. Không có transaction kiểu Firestore. Firestore hỗ trợ transaction nhiều document trên nhiều collection. Postgres dùng SQL transaction chuẩn, nhưng phương thức rpc() của Supabase client library gọi stored function — dùng stored procedure hoặc function Postgres cho các thao tác atomic nhiều bảng. Viết lại logic transaction thành stored function là phần tốn thời gian nhất khi migrate Firestore phức tạp.

8. RLS tắt theo mặc định. Supabase tạo bảng với Row Level Security tắt. Mọi bảng cần có RLS policy tường minh trước khi bạn có thể dùng anon key phía client. Bỏ sót điều này là rủi ro bảo mật, không phải rủi ro chức năng — ứng dụng vẫn chạy, nhưng mọi row đều có thể đọc công khai. Bật RLS trên mọi bảng ngay sau khi tạo:

ALTER TABLE posts ENABLE ROW LEVEL SECURITY;

CREATE POLICY "Users can read own posts"
  ON posts FOR SELECT
  USING (auth.uid() = user_id);

Thứ tự migrate

  1. Firebase Analytics → analytics bên ngoài (project song song, bắt đầu trước)
  2. Firebase Auth → Supabase Auth (user, hash, cấu hình lại OAuth)
  3. Firestore → Postgres (thiết kế schema, sau đó chạy ba giai đoạn)
  4. Cloud Storage → Supabase Storage (download → upload)
  5. Client SDK → thay import, cập nhật tham chiếu foreign key
  6. RLS policies → bật trước khi chuyển traffic production

Vội vàng làm bước 3–5 trước khi bước 2 hoàn tất khiến các tham chiếu user_id trỏ đến Firebase UID chưa tồn tại trong Supabase. Hãy làm Auth trước.

Tham khảo