# How to Get Document ID in Firestore

- Tool: Firebase
- Difficulty: Beginner
- Time required: 10-15 min
- Compatibility: Firebase (Spark and Blaze plans), firebase v9+ modular SDK
- Last updated: March 2026

## TL;DR

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.

## Before you start

- 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

### 1. 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.

```
import { collection, getDocs, getFirestore } from 'firebase/firestore'

const db = getFirestore()

async function getAllUsers() {
  const querySnapshot = await getDocs(collection(db, 'users'))

  querySnapshot.forEach((doc) => {
    console.log('Document ID:', doc.id)
    console.log('Document data:', doc.data())
  })

  // Or use the docs array for mapping:
  const users = querySnapshot.docs.map((doc) => ({
    id: doc.id,
    ...doc.data(),
  }))

  return users
}
```

**Expected result:** Each document's ID and data are logged, and the users array contains objects with the id property.

### 2. 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.

```
import { doc, getDoc, getFirestore } from 'firebase/firestore'

const db = getFirestore()

async function getUserById(userId: string) {
  const docRef = doc(db, 'users', userId)
  const docSnap = await getDoc(docRef)

  if (docSnap.exists()) {
    console.log('ID:', docSnap.id)       // Same as userId
    console.log('Data:', docSnap.data())
    return { id: docSnap.id, ...docSnap.data() }
  } else {
    console.log('Document not found')
    return null
  }
}
```

**Expected result:** The document ID and data are returned if the document exists, or null if not found.

### 3. 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.

```
import { collection, addDoc, getFirestore } from 'firebase/firestore'

const db = getFirestore()

async function createPost(title: string, content: string) {
  const docRef = await addDoc(collection(db, 'posts'), {
    title,
    content,
    createdAt: new Date(),
  })

  console.log('New document created with ID:', docRef.id)
  return docRef.id
}
```

**Expected result:** The new document is created and its auto-generated ID is returned.

### 4. 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.

```
import { collection, doc, setDoc, getFirestore } from 'firebase/firestore'

const db = getFirestore()

async function createUserWithKnownId() {
  // Generate a reference with an auto-ID (no network call yet)
  const newUserRef = doc(collection(db, 'users'))
  const userId = newUserRef.id

  console.log('Pre-generated ID:', userId)

  // Write the document, including its own ID as a field
  await setDoc(newUserRef, {
    id: userId,
    name: 'Alice',
    email: 'alice@example.com',
    createdAt: new Date(),
  })

  return userId
}
```

**Expected result:** A document is created with a known ID that is also stored as a field within the document.

### 5. 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.

```
import { collection, query, where, getDocs, documentId, getFirestore } from 'firebase/firestore'

const db = getFirestore()

async function getUsersByIds(userIds: string[]) {
  // 'in' operator supports up to 30 values
  const q = query(
    collection(db, 'users'),
    where(documentId(), 'in', userIds)
  )

  const snapshot = await getDocs(q)
  return snapshot.docs.map((doc) => ({
    id: doc.id,
    ...doc.data(),
  }))
}
```

**Expected result:** Only documents whose IDs match the provided list are returned.

## Complete code example

File: `firestore-document-ids.ts`

```typescript
import {
  getFirestore,
  collection,
  doc,
  addDoc,
  setDoc,
  getDoc,
  getDocs,
  query,
  where,
  documentId,
} from 'firebase/firestore'

const db = getFirestore()

export interface UserDoc {
  id: string
  name: string
  email: string
  createdAt: Date
}

// Get all documents with their IDs
export async function getAllUsers(): Promise<UserDoc[]> {
  const snapshot = await getDocs(collection(db, 'users'))
  return snapshot.docs.map((doc) => ({
    id: doc.id,
    ...(doc.data() as Omit<UserDoc, 'id'>),
  }))
}

// Get a single document by ID
export async function getUserById(userId: string): Promise<UserDoc | null> {
  const docSnap = await getDoc(doc(db, 'users', userId))
  if (!docSnap.exists()) return null
  return { id: docSnap.id, ...(docSnap.data() as Omit<UserDoc, 'id'>) }
}

// Create a document and get the auto-generated ID
export async function createUser(name: string, email: string): Promise<string> {
  const docRef = await addDoc(collection(db, 'users'), {
    name,
    email,
    createdAt: new Date(),
  })
  return docRef.id
}

// Pre-generate ID before writing
export async function createUserWithKnownId(
  name: string,
  email: string
): Promise<string> {
  const newRef = doc(collection(db, 'users'))
  await setDoc(newRef, { name, email, createdAt: new Date() })
  return newRef.id
}

// Query multiple documents by their IDs
export async function getUsersByIds(ids: string[]): Promise<UserDoc[]> {
  if (ids.length === 0) return []
  const chunks: string[][] = []
  for (let i = 0; i < ids.length; i += 30) {
    chunks.push(ids.slice(i, i + 30))
  }
  const results: UserDoc[] = []
  for (const chunk of chunks) {
    const q = query(collection(db, 'users'), where(documentId(), 'in', chunk))
    const snapshot = await getDocs(q)
    snapshot.forEach((doc) => {
      results.push({ id: doc.id, ...(doc.data() as Omit<UserDoc, 'id'>) })
    })
  }
  return results
}
```

## Common mistakes

- **Trying to access doc.id on the QuerySnapshot instead of individual documents** — undefined Fix: QuerySnapshot does not have an id property. Iterate over querySnapshot.docs or use querySnapshot.forEach to access each document's id individually.
- **Spreading doc.data() before setting the id, allowing a data field named 'id' to overwrite the document ID** — undefined Fix: 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.
- **Passing more than 30 values to the 'in' operator with documentId(), causing a runtime error** — undefined Fix: 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

## 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.

---

Source: https://www.rapidevelopers.com/firebase-tutorial/how-to-get-document-id-in-firestore
© RapidDev — https://www.rapidevelopers.com/firebase-tutorial/how-to-get-document-id-in-firestore
