Firexport가이드
Firestore 문서의 이름을 바꾸는 방법(ID 변경)
Firestore는 문서의 이름을 바꿀 수 없습니다. 문서 ID는 경로의 일부이고, Firebase 콘솔에도 SDK에도 이를 바꾸는 방법이 없습니다. 이름을 바꾸려면 새 ID로 문서를 다시 만들고 옛 문서를 삭제해야 하며, 하위 컬렉션 안의 모든 것도 함께 옮겨야 합니다. 문서를 삭제해도 하위 컬렉션은 건드리지 않기 때문입니다. 방법은 세 가지이며, 어느 것이 맞는지는 문서 아래에 얼마나 많은 것이 있는지에 달려 있습니다.
2026년 10월 27일 업데이트. 방법 1의 확장 프로그램 Firexport는 저희가 만든 것입니다. 방법 2와 3에는 필요 없습니다.
짧은 답
| 1. 콘솔 + 확장 프로그램 | 2. Admin SDK 스크립트 | 3. 콘솔에서 수동으로 | |
|---|---|---|---|
| 준비 | Chrome 확장 프로그램 설치, Google 계정 연결 | Node.js와 Google Cloud 사용자 인증 정보 | 없음 |
| 하위 컬렉션 | 모든 깊이까지 옮김 | 모든 깊이까지 옮김 | 옛 경로에 남음 |
| 필드 값 | 정확히 그대로 | 그대로, 단 2.0 같은 double은 정수가 됨 | 다시 입력 |
| 전부 되거나 하나도 안 됨 | 예, 쓰기 한 번으로 | 예, 배치 하나로(최대 10 MiB) | 아니요 |
| 규모 | 문서 250개까지 | 제한 없음 | 필드 몇 개 |
1. Firexport로 Firebase 콘솔에서 ID 바꾸기
Firexport는 Firestore 패널 뷰에서 각 문서의 ⋮ 메뉴에 문서 ID 변경을 추가하는 무료 Chrome 확장 프로그램입니다. 문서와 그 하위 컬렉션 안의 모든 것을 쓰기 한 번으로 새 ID에 옮기므로, 전부 되거나 하나도 되지 않습니다. 연결한 Google 계정으로 쓰므로 준비할 서비스 계정 키도, 코드도 없습니다.
- Chrome 웹 스토어에서 Firexport를 설치합니다.
- Firebase 콘솔에서 Firestore Database > 데이터(Data)를 패널 뷰(Panel view)로 열고 문서가 있는 컬렉션을 클릭합니다.
- 목록에서 문서 위에 마우스를 올리고 ID 왼쪽에 나타나는 ⋮를 클릭한 뒤 문서 ID 변경을 클릭합니다.
- 처음에는 창이 Google 계정 연결을 요청합니다. 콘솔에 로그인한 계정을 고르고 Google 창에서 액세스를 허용합니다.
- 새 문서 ID를 입력하고 ID 변경을 클릭합니다. 하위 컬렉션이 없는 문서는 바로 옮겨집니다.
- 하위 컬렉션이 있으면 Firexport가 먼저 그것을 읽고, 옮길 문서 수와 그에 드는 쓰기 횟수, 확인하는 데 쓴 읽기 횟수를 보여 줍니다. 옮기기를 클릭합니다. 그러면 콘솔이 새 ID의 문서를 엽니다.
옮기기가 멈추는 경우
- 새 ID(또는 그 아래)에 이미 문서가 있을 때. Firexport는 절대 덮어쓰지 않습니다. 쓰기가 거부되고 아무것도 바뀌지 않습니다.
- Firexport가 읽은 뒤에 문서 중 하나라도 바뀌었을 때. 그대로 쓰면 옛 내용이 새 ID에 들어가고 새 내용은 삭제되므로, 아무것도 쓰지 않습니다. 다시 시도할 수 있습니다.
- 문서가 250개를 넘거나, 쓰기 한 번에 담을 수 있는 크기(10 MiB)를 넘을 때. Firexport는 아무것도 쓰기 전에 멈춥니다. 방법 2를 쓰세요.
- Firestore가 허용하지 않는 ID일 때:
/가 들어 있거나, 정확히.또는..이거나,__로 시작하고 끝나거나, 1,500바이트를 넘는 ID. 양 끝의 공백도 ID의 일부이므로 입력란은 공백을 지우지 않습니다.
모든 필드는 값과 유형이 정확히 그대로 유지됩니다. Firexport는 콘솔에 표시된 것이 아니라 Firestore에서 읽은 것을 쓰기 때문입니다. 쓰기는 연결된 계정의 권한을 쓰므로 보안 규칙이 적용되지 않습니다. 문서는 브라우저에서 Google로 바로 가며, 저희에게는 오지 않습니다. 문서 ID 변경은 모든 플랜에서 무료입니다.
콘솔에서 바로, 하위 컬렉션까지 쓰기 한 번으로 Firestore 문서의 ID를 바꿔 보세요.
2. Admin SDK 스크립트로 옮기기
아래에 문서가 250개 넘게 있는 문서나, 이름을 바꿀 문서가 많을 때는 Firebase Admin SDK를 쓰는 Node.js 스크립트로 같은 이동을 할 수 있습니다. 아래 스크립트는 문서와 그 아래의 모든 하위 컬렉션을 읽은 뒤, 각 문서를 새 경로에 만들고 옛 문서를 삭제합니다. 새 경로가 다른 컬렉션에 있어도 되므로, 같은 스크립트로 문서를 옮길 수도 있습니다. Firestore에 쓸 수 있는 계정이 필요합니다. 프로젝트 소유자나 편집자, 또는 Cloud Datastore 사용자 역할입니다.
- SDK 설치:
npm install firebase-admin - 로컬 사용자 인증 정보로 로그인:
gcloud auth application-default login. 서버에서는 대신GOOGLE_APPLICATION_CREDENTIALS가 서비스 계정 키를 가리키게 합니다. - 아래 스크립트를
rename-document.mjs로 저장하고your-project-id를 바꾼 뒤node rename-document.mjs users/alice users/alice-smith를 실행합니다.
// Usage: node rename-document.mjs users/alice users/alice-smith → moves users/alice and everything under it
// Firestore has no rename: this creates the document at the new path with all its subcollections, then deletes the old ones.
import { initializeApp } from 'firebase-admin/app';
import { getFirestore } from 'firebase-admin/firestore';
initializeApp({ projectId: 'your-project-id' });
const db = getFirestore();
db.settings({ useBigInt: true }); // integers past 2^53 would otherwise lose their last digits on the way through
const [from, to] = process.argv.slice(2);
if (!from || !to) throw new Error('Usage: node rename-document.mjs <document path> <new document path>');
if (to === from || to.startsWith(`${from}/`)) throw new Error('The new path must be outside the document you move');
// true: one batch, so every write happens or none does (up to 10 MiB of data in all).
// false: for more than that. Copies everything first and deletes the old documents only once every copy is written.
const ONE_BATCH = true;
// The document and every document under it, in subcollections of subcollections too. listDocuments() also returns
// places that have subcollections but no fields, so the walk reaches what is under them; the places themselves aren't copied.
async function collect(ref, found) {
const snapshot = await ref.get();
if (snapshot.exists) found.push(snapshot);
for (const collection of await ref.listCollections()) {
for (const child of await collection.listDocuments()) await collect(child, found);
}
return found;
}
const docs = await collect(db.doc(from), []);
if (docs[0]?.ref.path !== from) throw new Error(`${from} doesn't exist`);
const target = (doc) => db.doc(to + doc.ref.path.slice(from.length));
if (ONE_BATCH) {
// create() fails if a document is already at the new path, and lastUpdateTime fails if one changed after it was read.
// Either way Firestore rejects the whole batch and nothing is written.
const batch = db.batch();
for (const doc of docs) batch.create(target(doc), doc.data());
for (const doc of docs) batch.delete(doc.ref, { lastUpdateTime: doc.updateTime });
await batch.commit();
} else {
// BulkWriter keeps under Firestore's rate limits and retries what can be retried, but it isn't all or nothing.
const writer = db.bulkWriter();
const failures = [];
for (const doc of docs) {
writer.create(target(doc), doc.data()).catch((error) => failures.push(`${doc.ref.path}: ${error.message}`));
}
await writer.flush();
if (failures.length) {
failures.forEach((failure) => console.log(failure));
throw new Error('Some copies failed, so nothing was deleted. Delete the copies that were written, then try again.');
}
for (const doc of docs) {
writer.delete(doc.ref, { lastUpdateTime: doc.updateTime }).catch((error) => failures.push(`${doc.ref.path}: ${error.message}`));
}
await writer.close();
failures.forEach((failure) => console.log(`Not deleted, ${failure}`));
}
console.log(`Moved ${docs.length} documents from ${from} to ${to}`);listDocuments()는 자기 필드 없이 하위 컬렉션만 있는 자리(콘솔에서 ID가 기울임꼴로 보이는 것)도 돌려주므로, 스크립트가 그 아래의 문서까지 닿습니다. 그 자리 자체는 복사되지 않습니다. 하위 컬렉션이 새 경로에 생기면 그 자리도 함께 나타납니다.ONE_BATCH를 켜 두면 모든 생성과 삭제가 배치 하나에 들어갑니다. 새 경로에 이미 문서가 있거나 스크립트가 읽은 뒤에 문서가 바뀌었으면, Firestore가 배치를 거부하고 아무것도 쓰이지 않습니다.- 배치 하나에는 최대 10 MiB까지 담을 수 있습니다. 그보다 큰 트리라면
ONE_BATCH를false로 바꿉니다. 스크립트가BulkWriter로 모두 복사하고, 모든 복사본이 쓰였을 때만 옛 문서를 삭제합니다. 이 방식은 전부 되거나 하나도 안 되는 방식이 아니므로, 실행하는 동안 그 문서들에 쓰지 마세요. - 방법 1처럼 보안 규칙을 거치지 않습니다. 타임스탬프, 참조, 지리적 위치, 바이트는 유형을 그대로 유지하고,
useBigInt는 253을 넘는 정수도 정확히 유지합니다. 다만 Admin SDK가 지키지 못하는 것이 하나 있습니다.2.0처럼 소수부가 없는 double은 일반 숫자로 읽혀 정수2로 다시 쓰입니다.
3. 콘솔에서 수동으로 복사하기
필드가 몇 개뿐이고 하위 컬렉션이 없는 문서라면 콘솔만으로 충분합니다. 서로 묶이지 않은 쓰기 두 번이 필요하고, 모든 값을 다시 입력해야 합니다.
- 문서를 열고 각 필드의 이름, 유형, 값을 적어 둡니다.
- 같은 컬렉션에서 문서 추가(Add document)를 클릭하고 문서 ID(Document ID)에 새 ID를 입력한 뒤, 같은 필드를 같은 유형으로 추가합니다. 저장(Save)을 클릭합니다.
- 새 문서를 확인한 다음 옛 문서의 ⋮ 메뉴를 열고 문서 삭제(Delete document)를 클릭합니다.
입력하면서 유형을 확인하세요. 문자열 필드에 입력한 숫자는 문자열로 남고, 타임스탬프는 타임스탬프 유형으로 다시 입력해야 합니다. 하위 컬렉션은 따라오지 않습니다. 하위 컬렉션마다 같은 방식으로 문서를 하나씩 복사해야 합니다.
새 ID로 달라지는 것
어떤 방법으로 옮기든 새 ID의 문서는 새 문서입니다. 그래서 몇 가지가 달라집니다.
- 생성 시각. 옮긴 문서마다 생성 시각과 업데이트 시각이 새로 정해집니다.
- 참조. 다른 문서의 참조 필드는 여전히 옛 경로를 가리키고,
ownerId처럼 문자열로 저장한 ID도 마찬가지입니다. 직접 고쳐야 합니다. - Cloud Functions와 리스너. 옮긴 문서마다 생성 트리거와 삭제 트리거가 실행되고, 컬렉션에 리스너를 단 앱에는 문서 하나가 삭제되고 다른 하나가 추가된 것으로 보입니다.
- 비용. 옮긴 문서마다 쓰기 1회와 삭제 1회가 과금되고, 문서를 찾고 읽는 데 드는 읽기가 더해집니다.
FAQ
Firestore에서 문서 이름을 바꿀 수 있나요?
바로는 안 됩니다. Firestore에는 이름 바꾸기 작업이 없고, 문서 ID는 한 번 만들어지면 바꿀 수 없습니다. 이름을 바꾸려면 새 ID로 같은 데이터를 담은 문서를 만든 뒤 옛 문서를 삭제합니다. Firexport의 문서 ID 변경과 위의 Admin SDK 스크립트는 이 둘을 쓰기 한 번으로 처리합니다.
문서 이름을 바꾸면 하위 컬렉션도 옮겨지나요?
하위 컬렉션도 함께 옮길 때만 그렇습니다. 하위 컬렉션은 문서의 데이터가 아니라 경로에 속하므로, 필드를 복사하면 하위 컬렉션은 옛 자리에 남고, 옛 문서를 삭제해도 하위 컬렉션은 삭제되지 않습니다. Firexport와 위의 스크립트는 모든 깊이의 하위 컬렉션을 문서와 함께 옮깁니다.
Firestore에서 컬렉션 이름을 바꿀 수 있나요?
아니요. 컬렉션은 그 안의 문서를 통해서만 존재하므로, 컬렉션 이름을 바꾸려면 모든 문서를 새 컬렉션 이름으로 옮겨야 합니다. 콘솔도 Firexport도 이를 한 번에 하지는 못합니다. 문서마다 위의 이동을 실행하는 스크립트라면 할 수 있습니다.
옛 문서를 가리키던 참조는 어떻게 되나요?
이동한 뒤에는 더 이상 존재하지 않는 옛 경로를 계속 가리킵니다. Firestore는 다른 문서에 저장된 참조 필드나 ID를 고쳐 주지 않으므로, 이름을 바꾼 뒤 직접 찾아서 고쳐야 합니다.
Firestore 문서 이름을 바꾸는 데 비용이 얼마나 드나요?
옮기는 문서 하나가 쓰기 1회와 삭제 1회이고, 여기에 그 문서와 하위 컬렉션을 찾는 읽기가 더해집니다. Firexport는 옮기기를 누르기 전에 그 수를 보여 줍니다. 무료 Spark 요금제에서는 하루 쓰기 20,000회와 삭제 20,000회에서 차감됩니다.
