Firexport指南

如何將 CSV 和 JSON 匯入 Firestore

Firebase 控制台只能用新增文件(Add document)一次新增一份 Firestore 文件,沒有載入檔案的按鈕。Google 自己的匯入功能 gcloud firestore import 只讀取 Firestore 自己的匯出檔案,不讀取 CSV 或 JSON。要把檔案放進集合,有三種方法。哪一種合適,取決於檔案的大小和它的來源。

更新於 2026年10月21日。 方法 1 中的擴充功能 Firexport 是我們開發的。方法 2 和 3 不需要它。

簡短回答

  • 現在就要把一個 CSV 或 JSON 檔案匯入一個集合: 用瀏覽器擴充功能從控制台的面板檢視(Panel view)匯入。 方法 1
  • 非常大的檔案、排程執行的工作,或依自己的方式轉換資料: 一段簡短的 Admin SDK 指令碼。 方法 2
  • 從另一個專案或備份搬入整個資料庫: 用 gcloud 代管匯出和匯入(它不讀取 CSV 或 JSON)。 方法 3
1. 控制台 + 擴充功能2. Admin SDK 指令碼3. gcloud 代管匯入
準備工作安裝一個 Chrome 擴充功能,連結你的 Google 帳戶Node.js 和 Google Cloud 憑證Cloud Storage 值區和 gcloud
可讀取CSV、JSON、JSON Lines 或貼上的文字CSV、JSON、JSON Lines(你的程式碼讀取的格式)只有 Firestore 自己的匯出檔案
欄位類型逐一為每個欄位選擇,並可預覽由你的程式碼轉換沿用匯出時的類型
現有文件保留、取代或合併建立、設定或合併會被覆寫
需要啟用計費不需要不需要需要(Blaze 方案)

1. 用 Firexport 從 Firebase 控制台匯入

Firexport 是一款免費的 Chrome 擴充功能,會在 Firestore 的面板檢視(Panel view)中加入一個匯入圖示,位於每個集合新增文件(Add document)那一列的右端。它用你連結的 Google 帳戶寫入,所以不需要設定服務帳戶金鑰,也不需要寫程式碼。

  1. 從 Chrome 線上應用程式商店安裝 Firexport。
  2. 在 Firebase 控制台開啟 Firestore Database > 資料(Data)的面板檢視(Panel view),然後按一下要匯入的集合。
  3. 按一下新增文件(Add document)那一列右端的匯入圖示。
  4. 選擇或拖放一個 CSV、JSON 或 JSON Lines 檔案,或者切換到貼上文字,然後按一下讀取。
  5. 確認每個欄位的類型和文件 ID 的來源,並選擇文件已存在時要怎麼處理。
  6. 第一次使用時,視窗會要求你連結 Google 帳戶:選擇可以寫入該專案的帳戶,並在 Google 的視窗中允許存取。然後按一下匯入。
Firebase 控制台中 Firestore 的面板檢視(Panel view),以及 Firexport 在新增文件(Add document)那一列末端加入的匯入圖示
匯入圖示就在你要填入資料的那個集合裡,位於新增文件(Add document)的右端。
讀取 CSV 後的匯入視窗:三份文件、八個欄位,每個欄位都已選好類型
每一欄都會成為一個欄位,並附上建議的類型。Document ID 欄會成為文件 ID。

欄位類型和文件 ID

  • CSV 的儲存格都是文字,JSON 也沒有時間戳記或參照。所以 Firexport 會依每個欄位的值建議一個類型:字串、整數、數字 (double)、布林值、時間戳記、參照、地理點或位元組 (base64),或者「不匯入」。只有在每個值都符合時才會建議該類型,而且每一個你都可以更改。
  • 時間戳記可以是 ISO 8601(2026-10-01T09:30:00Z)、瀏覽器時區的 2026-10-01 09:30:00,或毫秒數。參照可以是 users/u1 這樣的路徑。對應和陣列中的值會維持 JSON 類型,所以巢狀資料應該放在 JSON 裡:CSV 的一欄永遠是一個頂層欄位。
  • 文件 ID 可以來自你選擇的一欄、JSON 物件的鍵,或是新的隨機 ID。Document ID 欄會自動被選為 ID 的來源,所以 Firexport 匯出的檔案可以直接匯入。以路徑形式(例如 /users/u1)寫的 ID 會使用最後一個區段。
  • 預覽會顯示第一份文件寫入後的樣子,以及每個值的類型。無法寫入的列(例如值不符合其類型,或 ID 重複)會被列出,並在送出任何資料之前排除。

文件已存在時

預設的保留原文件只會建立新文件:ID 已存在的列會失敗並列出。取代會讓文件的欄位與檔案中的完全一致。合併會寫入檔案中的欄位(包括對應內部),並保留文件的其他欄位。新的隨機 ID 不會與現有文件重複。

現有文件的三個選項、第一份文件及其類型的預覽,以及「匯入 3 份文件」按鈕
開始之前,視窗會告訴你文件將寫入哪裡。

每份文件算一次 Firestore 寫入,費用計入該專案。Firexport 每秒最多送出 500 次寫入,並重試因暫時性原因失敗的寫入。所有列都寫入後,視窗會關閉並顯示簡短的通知;如果有失敗的列,視窗會列出它們、提供 CSV 報告,並可以繼續匯入。寫入使用已連結帳戶的權限,所以不受安全性規則限制。你的檔案從瀏覽器直接送到 Google,從不經過我們。檔案和貼上的文字最大可達 100 MB。

不用指令碼、不用服務帳戶金鑰,直接從控制台把 CSV 或 JSON 檔案匯入 Firestore。

加到 Chrome — 免費

2. 用 Admin SDK 指令碼匯入

如果檔案超過 100 MB、匯入需要排程執行,或者要依自己的方式轉換資料,一段使用 Firebase Admin SDK 的 Node.js 指令碼可以自己寫入這些列。下面這段指令碼可以讀取含標題列的 CSV、JSON 陣列、以文件 ID 為鍵的 JSON 物件,或 JSON Lines,並為每一列寫入一份文件。它需要一個能寫入 Firestore 的帳戶:專案 Owner 或 Editor,或者 Cloud Datastore User 角色。

  1. 安裝 SDK 和 CSV 剖析器:npm install firebase-admin csv-parse
  2. 登入以取得本機憑證:gcloud auth application-default login。在伺服器上,改為讓 GOOGLE_APPLICATION_CREDENTIALS 指向服務帳戶金鑰。
  3. 把下面的指令碼存成 import-to-firestore.mjs,替換 your-project-id,把 TYPES 設為你的欄位,然後執行 node import-to-firestore.mjs orders.csv orders。
// Usage: node import-to-firestore.mjs orders.csv orders   → one document per row in orders
// Reads .csv (with a header row), .json (an array, or an object keyed by document ID) and .jsonl.
import { readFileSync } from 'node:fs';
import { initializeApp } from 'firebase-admin/app';
import { getFirestore, Timestamp } from 'firebase-admin/firestore';
import { parse } from 'csv-parse/sync';

initializeApp({ projectId: 'your-project-id' });
const db = getFirestore();
const [file, collection] = process.argv.slice(2);
if (!file || !collection) throw new Error('Usage: node import-to-firestore.mjs <file> <collection>');

// The column or key that holds each document's ID. Rows without one get a random ID.
const ID_FIELD = 'Document ID';
// CSV cells are all text and JSON has no timestamps: list the fields to convert. The rest stay as they are.
const TYPES = { createdAt: 'timestamp', items: 'integer', total: 'number' };
// 'create' never touches an existing document (that row fails), 'set' replaces it, 'merge' adds to it.
const MODE = 'create';

const text = readFileSync(file, 'utf8');
const isCsv = file.endsWith('.csv');
let rows;
if (isCsv) {
  rows = parse(text, { columns: true, bom: true, skip_empty_lines: true });
} else if (file.endsWith('.jsonl')) {
  rows = text.split('\n').filter((line) => line.trim()).map((line) => JSON.parse(line));
} else {
  const json = JSON.parse(text);
  rows = Array.isArray(json) ? json : Object.entries(json).map(([id, data]) => ({ [ID_FIELD]: id, ...data }));
}

function convert(type, value) {
  if (type === 'timestamp') {
    const date = new Date(/^\d+$/.test(value) ? Number(value) : value); // ISO 8601 or milliseconds
    if (Number.isNaN(date.getTime())) throw new Error(`not a timestamp: ${value}`);
    return Timestamp.fromDate(date);
  }
  if (type === 'integer' || type === 'number') {
    const number = Number(value);
    if (value === '' || !Number.isFinite(number) || (type === 'integer' && !Number.isInteger(number))) {
      throw new Error(`not ${type === 'integer' ? 'an integer' : 'a number'}: ${value}`);
    }
    return number;
  }
  if (type === 'boolean') return value === true || value === 'true';
  return value;
}

// Convert every row before writing anything, so a bad value stops the import instead of half of it.
const docs = rows.map((row, index) => {
  const data = {};
  for (const [field, value] of Object.entries(row)) {
    if (field === ID_FIELD || (isCsv && value === '')) continue; // an empty CSV cell is a missing field
    try {
      data[field] = TYPES[field] ? convert(TYPES[field], value) : value;
    } catch (error) {
      throw new Error(`Row ${index + 1}, ${field}: ${error.message}`);
    }
  }
  const id = row[ID_FIELD] ? String(row[ID_FIELD]).split('/').pop() : undefined; // a path keeps its last part
  return { row: index + 1, ref: id ? db.collection(collection).doc(id) : db.collection(collection).doc(), data };
});

// BulkWriter sends the writes in batches, keeps under Firestore's rate limits and retries what can be retried.
const writer = db.bulkWriter();
const failures = [];
for (const { row, ref, data } of docs) {
  const write = MODE === 'create' ? writer.create(ref, data) : writer.set(ref, data, { merge: MODE === 'merge' });
  write.catch((error) => failures.push(`Row ${row} (${ref.id}): ${error.message}`));
}
await writer.close();
console.log(`Wrote ${docs.length - failures.length} of ${docs.length} documents to ${collection}`);
failures.forEach((failure) => console.log(failure));
  • CSV 的儲存格都是文字:指令碼只轉換 TYPES 中列出的欄位,其餘的保留為字串。空白的儲存格不會寫成欄位。JSON 的值會維持原本的類型,對應和陣列會原樣寫入,只有直接位於另一個陣列中的陣列例外,因為 Firestore 不接受。
  • 指令碼會在寫入任何資料之前轉換每一列,所以遇到錯誤的值時,它會停下來並指出所在的列和欄位,而不是留下只完成一半的匯入。
  • 把 MODE 設為 'create' 時,現有文件不會被更動,它們的列會列為失敗。'set' 會取代它們,'merge' 會加入檔案中的欄位。沒有 ID 的列每次執行都會得到新的隨機 ID,所以執行兩次指令碼會讓它們重複。
  • BulkWriter 會把寫入分批,從每秒 500 次開始,在 Firestore 的 500/50/5 規則允許的範圍內逐步提高,並重試因暫時性原因失敗的寫入。集合可以是子集合路徑,例如 users/u1/orders。和方法 1 一樣,它會略過安全性規則,每份文件算一次寫入。

3. 用 gcloud 代管匯入複製整個資料庫

gcloud firestore import(也可以在 Google Cloud 控制台中 Firestore 的匯入/匯出頁面使用)是 Firestore 的代管匯入。它只讀取 gcloud firestore export 以 Firestore 自己的格式寫入 Cloud Storage 值區的內容,不讀取 CSV 或 JSON。用它來還原備份,或把集合從一個專案或資料庫複製到另一個,並完整保留它們的類型。

# 1. Export the collection from the source project.
gcloud firestore export gs://YOUR_BUCKET/orders-export --collection-ids=orders --project=source-project-id

# 2. Import that export into the destination project.
gcloud firestore import gs://YOUR_BUCKET/orders-export --project=destination-project-id
  • 需要啟用計費(Blaze 方案)。匯入時每份文件算一次寫入。
  • 匯入會覆寫 ID 相同的文件,其他文件則保持不變。它不會觸發 Cloud Functions。
  • 你的帳戶需要 Owner、Cloud Datastore Owner 或 Cloud Datastore Import Export Admin 角色。要從另一個專案的值區匯入,目標專案的 Firestore 服務代理人 service-PROJECT_NUMBER@gcp-sa-firestore.iam.gserviceaccount.com 需要能存取該值區。

常見問題

Firebase 控制台能把 CSV 或 JSON 檔案匯入 Firestore 嗎?

不能。Firestore 的「資料」分頁只能用新增文件(Add document)一次新增一份文件,而 gcloud firestore import 只讀取 Firestore 自己的匯出檔案。要載入 CSV 或 JSON 檔案,需要 Admin SDK 指令碼,或是在控制台加入匯入功能的瀏覽器擴充功能,例如 Firexport。

如何把 JSON 檔案匯入 Firestore?

使用 JSON 物件陣列(每份文件一個物件),或以文件 ID 為鍵的物件。在 Firexport 中,按一下集合新增文件(Add document)那一列上的匯入圖示,然後選擇檔案;對應和陣列會維持 JSON 類型。使用 Admin SDK 時,讀取檔案,再像上面的指令碼那樣用 BulkWriter 寫入每個物件。

時間戳記和參照要怎麼匯入?

CSV 和 JSON 無法攜帶 Firestore 的類型,所以檔案中的時間戳記在被轉換之前只是文字。在 Firexport 中,把欄位的類型設為時間戳記、參照或地理點,它就會進行轉換。在指令碼中,請自行轉換,例如用 Timestamp.fromDate(new Date(value)) 或 db.doc(path)。

匯入會受安全性規則限制嗎?

不會。Firexport 的匯入、Admin SDK 和 gcloud 都以某個帳戶的 IAM 權限寫入,而安全性規則只適用於用戶端 SDK。匯入之前,請先檢查資料和帳戶。

匯入 Firestore 要花多少錢?

無論使用哪種方法,每份匯入的文件都算一次寫入。在免費的 Spark 方案中,這會從每天 20,000 次寫入的配額中扣除,超過之後的寫入會失敗,直到配額重設為止。在 Blaze 方案中,超出免費額度的寫入會依資料庫位置的費率計費。