To get a document ID in Firestore, access the id property on a DocumentSnapshot or QueryDocumentSnapshot. When querying with getDocs, iterate over the snapshot and read doc.id for each document. When adding with addDoc, the returned DocumentReference contains the auto-generated ID. You can also use the documentId() sentinel in queries to filter by document ID directly.
Getting Document IDs in Firestore Queries and Writes
Every Firestore document has a unique ID within its collection. This tutorial covers all the ways to access and use document IDs: reading IDs from query results, getting the auto-generated ID after creating a document, querying by document ID, and deciding when to use custom IDs instead of auto-generated ones.
Prerequisites
- A Firebase project with Firestore enabled
- The firebase npm package installed (v9 or later)
- Firebase initialized in your app with a valid config object
- At least one collection with documents in Firestore
Step-by-step guide
Get document IDs from a query result
Get document IDs from a query result
When you call getDocs on a query or collection reference, you get a QuerySnapshot containing an array of QueryDocumentSnapshot objects. Each snapshot has an id property that contains the document's ID within its collection. Use forEach or the docs array to iterate over results and access both the ID and the data.
1import { collection, getDocs, getFirestore } from 'firebase/firestore'23const db = getFirestore()45async function getAllUsers() {6 const querySnapshot = await getDocs(collection(db, 'users'))78 querySnapshot.forEach((doc) => {9 console.log('Document ID:', doc.id)10 console.log('Document data:', doc.data())11 })1213 // Or use the docs array for mapping:14 const users = querySnapshot.docs.map((doc) => ({15 id: doc.id,16 ...doc.data(),17 }))1819 return users20}Expected result: Each document's ID and data are logged, and the users array contains objects with the id property.
Get the document ID from a single document read
Get the document ID from a single document read
When you use getDoc to read a single document by reference, the returned DocumentSnapshot also has the id property. Check snapshot.exists() first to confirm the document exists before accessing data. The document ID matches the last segment of the reference path.
1import { doc, getDoc, getFirestore } from 'firebase/firestore'23const db = getFirestore()45async function getUserById(userId: string) {6 const docRef = doc(db, 'users', userId)7 const docSnap = await getDoc(docRef)89 if (docSnap.exists()) {10 console.log('ID:', docSnap.id) // Same as userId11 console.log('Data:', docSnap.data())12 return { id: docSnap.id, ...docSnap.data() }13 } else {14 console.log('Document not found')15 return null16 }17}Expected result: The document ID and data are returned if the document exists, or null if not found.
Get the auto-generated ID after adding a document
Get the auto-generated ID after adding a document
When you use addDoc to create a document, Firestore auto-generates a unique 20-character ID. The returned DocumentReference contains this ID in its id property. You can use this ID immediately to update the document or store it as a reference in another document.
1import { collection, addDoc, getFirestore } from 'firebase/firestore'23const db = getFirestore()45async function createPost(title: string, content: string) {6 const docRef = await addDoc(collection(db, 'posts'), {7 title,8 content,9 createdAt: new Date(),10 })1112 console.log('New document created with ID:', docRef.id)13 return docRef.id14}Expected result: The new document is created and its auto-generated ID is returned.
Generate an ID before writing with doc()
Generate an ID before writing with doc()
Sometimes you need the document ID before writing the data, for example to include the ID as a field inside the document or to reference it in another document in the same batch. Call doc() on a collection reference without arguments to generate a new DocumentReference with an auto-generated ID. Then use setDoc to write data to that reference.
1import { collection, doc, setDoc, getFirestore } from 'firebase/firestore'23const db = getFirestore()45async function createUserWithKnownId() {6 // Generate a reference with an auto-ID (no network call yet)7 const newUserRef = doc(collection(db, 'users'))8 const userId = newUserRef.id910 console.log('Pre-generated ID:', userId)1112 // Write the document, including its own ID as a field13 await setDoc(newUserRef, {14 id: userId,15 name: 'Alice',16 email: 'alice@example.com',17 createdAt: new Date(),18 })1920 return userId21}Expected result: A document is created with a known ID that is also stored as a field within the document.
Query documents by their ID using documentId()
Query documents by their ID using documentId()
Firestore provides the documentId() sentinel for querying by document ID in where clauses. This is useful for fetching a set of documents by their IDs (for example, from a list of references stored in another document). Use the 'in' operator to fetch up to 30 documents by ID in a single query.
1import { collection, query, where, getDocs, documentId, getFirestore } from 'firebase/firestore'23const db = getFirestore()45async function getUsersByIds(userIds: string[]) {6 // 'in' operator supports up to 30 values7 const q = query(8 collection(db, 'users'),9 where(documentId(), 'in', userIds)10 )1112 const snapshot = await getDocs(q)13 return snapshot.docs.map((doc) => ({14 id: doc.id,15 ...doc.data(),16 }))17}Expected result: Only documents whose IDs match the provided list are returned.
Complete working example
1import {2 getFirestore,3 collection,4 doc,5 addDoc,6 setDoc,7 getDoc,8 getDocs,9 query,10 where,11 documentId,12} from 'firebase/firestore'1314const db = getFirestore()1516export interface UserDoc {17 id: string18 name: string19 email: string20 createdAt: Date21}2223// Get all documents with their IDs24export async function getAllUsers(): Promise<UserDoc[]> {25 const snapshot = await getDocs(collection(db, 'users'))26 return snapshot.docs.map((doc) => ({27 id: doc.id,28 ...(doc.data() as Omit<UserDoc, 'id'>),29 }))30}3132// Get a single document by ID33export async function getUserById(userId: string): Promise<UserDoc | null> {34 const docSnap = await getDoc(doc(db, 'users', userId))35 if (!docSnap.exists()) return null36 return { id: docSnap.id, ...(docSnap.data() as Omit<UserDoc, 'id'>) }37}3839// Create a document and get the auto-generated ID40export async function createUser(name: string, email: string): Promise<string> {41 const docRef = await addDoc(collection(db, 'users'), {42 name,43 email,44 createdAt: new Date(),45 })46 return docRef.id47}4849// Pre-generate ID before writing50export async function createUserWithKnownId(51 name: string,52 email: string53): Promise<string> {54 const newRef = doc(collection(db, 'users'))55 await setDoc(newRef, { name, email, createdAt: new Date() })56 return newRef.id57}5859// Query multiple documents by their IDs60export async function getUsersByIds(ids: string[]): Promise<UserDoc[]> {61 if (ids.length === 0) return []62 const chunks: string[][] = []63 for (let i = 0; i < ids.length; i += 30) {64 chunks.push(ids.slice(i, i + 30))65 }66 const results: UserDoc[] = []67 for (const chunk of chunks) {68 const q = query(collection(db, 'users'), where(documentId(), 'in', chunk))69 const snapshot = await getDocs(q)70 snapshot.forEach((doc) => {71 results.push({ id: doc.id, ...(doc.data() as Omit<UserDoc, 'id'>) })72 })73 }74 return results75}Common mistakes when getting Document ID in Firestore
Why it's a problem: Trying to access doc.id on the QuerySnapshot instead of individual documents
How to avoid: QuerySnapshot does not have an id property. Iterate over querySnapshot.docs or use querySnapshot.forEach to access each document's id individually.
Why it's a problem: Spreading doc.data() before setting the id, allowing a data field named 'id' to overwrite the document ID
How to avoid: Always put the id field first: { id: doc.id, ...doc.data() }. Or use a different property name for any id field stored in the document data.
Why it's a problem: Passing more than 30 values to the 'in' operator with documentId(), causing a runtime error
How to avoid: Split the ID array into chunks of 30 and run separate queries for each chunk. Merge the results into a single array.
Best practices
- Always include the document ID when mapping query results to your app's data model
- Use addDoc for auto-generated IDs and setDoc with doc() for custom or pre-generated IDs
- When you need the ID before writing, use doc(collection(db, 'collectionName')) to generate it locally
- Chunk documentId() 'in' queries into groups of 30 to stay within Firestore limits
- Use TypeScript interfaces that include an id field to keep document types consistent
- Consider using meaningful custom IDs (like user UIDs) instead of auto-generated IDs when there is a natural key
Still stuck?
Copy one of these prompts to get a personalized, step-by-step explanation.
How do I get the document ID from Firestore query results in the modular SDK v9? Show me how to map documents with their IDs, get the auto-generated ID after addDoc, and query by document ID using documentId().
Create a TypeScript module for Firestore that provides functions for getting documents with their IDs, creating documents with auto-generated and pre-generated IDs, and querying multiple documents by ID using documentId() with chunking for large arrays.
Frequently asked questions
What format are Firestore auto-generated document IDs?
Auto-generated IDs are 20-character strings using Base62 encoding (alphanumeric characters). They are designed to be unique and roughly time-ordered, which helps with index locality.
Can I change a document's ID after creating it?
No, document IDs are immutable in Firestore. To change an ID, you must create a new document with the desired ID, copy the data, and delete the old document.
Should I store the document ID as a field inside the document?
It is not required since you can always access it from the snapshot. However, storing it as a field can be convenient when the data is sent to external systems or serialized to JSON where the ID context would be lost.
Is there a performance difference between auto-generated and custom IDs?
Auto-generated IDs are optimized for write throughput because they are roughly time-ordered and distribute writes across the key space. Sequential custom IDs (like 1, 2, 3) can cause write hotspots.
How many document IDs can I pass to the 'in' operator?
The 'in' operator supports up to 30 values per query. For larger lists, split into chunks of 30, run parallel queries, and merge the results.
Can RapidDev help design an efficient Firestore data model?
Yes, RapidDev's engineering team can help design Firestore document structures, ID strategies, and query patterns optimized for your specific use case.
Talk to an Expert
Our team has built 600+ apps. Get personalized help with your project.
Book a free consultation