如何將 CSV 和 JSON 匯入 Firestore
Firebase 控制台只能用新增文件(Add document)一次新增一份 Firestore 文件,沒有載入檔案的按鈕。Google 自己的匯入功能 gcloud firestore import 只讀取 Firestore 自己的匯出檔案,不讀取 CSV 或 JSON。要把檔案放進集合,有三種方法。哪一種合適,取決於檔案的大小和它的來源。
更新於 2026年10月21日。 方法 1 中的擴充功能 Firexport 是我們開發的。方法 2 和 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 帳戶寫入,所以不需要設定服務帳戶金鑰,也不需要寫程式碼。
- 從 Chrome 線上應用程式商店安裝 Firexport。
- 在 Firebase 控制台開啟 Firestore Database > 資料(Data)的面板檢視(Panel view),然後按一下要匯入的集合。
- 按一下新增文件(Add document)那一列右端的匯入圖示。
- 選擇或拖放一個 CSV、JSON 或 JSON Lines 檔案,或者切換到貼上文字,然後按一下讀取。
- 確認每個欄位的類型和文件 ID 的來源,並選擇文件已存在時要怎麼處理。
- 第一次使用時,視窗會要求你連結 Google 帳戶:選擇可以寫入該專案的帳戶,並在 Google 的視窗中允許存取。然後按一下匯入。


欄位類型和文件 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 不會與現有文件重複。

每份文件算一次 Firestore 寫入,費用計入該專案。Firexport 每秒最多送出 500 次寫入,並重試因暫時性原因失敗的寫入。所有列都寫入後,視窗會關閉並顯示簡短的通知;如果有失敗的列,視窗會列出它們、提供 CSV 報告,並可以繼續匯入。寫入使用已連結帳戶的權限,所以不受安全性規則限制。你的檔案從瀏覽器直接送到 Google,從不經過我們。檔案和貼上的文字最大可達 100 MB。
不用指令碼、不用服務帳戶金鑰,直接從控制台把 CSV 或 JSON 檔案匯入 Firestore。
2. 用 Admin SDK 指令碼匯入
如果檔案超過 100 MB、匯入需要排程執行,或者要依自己的方式轉換資料,一段使用 Firebase Admin SDK 的 Node.js 指令碼可以自己寫入這些列。下面這段指令碼可以讀取含標題列的 CSV、JSON 陣列、以文件 ID 為鍵的 JSON 物件,或 JSON Lines,並為每一列寫入一份文件。它需要一個能寫入 Firestore 的帳戶:專案 Owner 或 Editor,或者 Cloud Datastore User 角色。
- 安裝 SDK 和 CSV 剖析器:
npm install firebase-admin csv-parse - 登入以取得本機憑證:
gcloud auth application-default login。在伺服器上,改為讓GOOGLE_APPLICATION_CREDENTIALS指向服務帳戶金鑰。 - 把下面的指令碼存成
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 方案中,超出免費額度的寫入會依資料庫位置的費率計費。
