# How to Add a New Document in Firestore

- Tool: Firebase
- Difficulty: Beginner
- Time required: 10-15 min
- Compatibility: Firebase (all plans), firebase 10.x+, Firestore modular SDK v9+
- Last updated: March 2026

## TL;DR

Add documents to Firestore using addDoc() for auto-generated IDs or setDoc() for custom IDs. Import these functions from firebase/firestore along with collection and doc references. addDoc returns a DocumentReference with the new ID, while setDoc overwrites the entire document at a specific path. Always configure Firestore security rules to control who can create documents, and use serverTimestamp() for consistent timestamps.

## Creating Documents in Firestore with addDoc and setDoc

Firestore offers two ways to add documents: addDoc() generates a unique ID automatically, and setDoc() lets you specify the document ID. This tutorial covers both methods using the modular v9+ SDK, shows how to include server timestamps, handle errors, and write the security rules that allow writes. You will build a working example that creates documents in a collection and reads back the generated ID.

## Before you start

- A Firebase project with Firestore enabled (Firebase Console > Firestore Database > Create database)
- Firebase SDK installed in your project (npm install firebase)
- A firebase.ts initialization module with getFirestore exported
- Basic TypeScript/JavaScript knowledge

## Step-by-step guide

### 1. Add a document with an auto-generated ID using addDoc

Use addDoc() when you want Firestore to generate a unique document ID for you. Pass a collection reference and the data object. addDoc returns a Promise that resolves to a DocumentReference containing the auto-generated ID. This is the most common way to add documents because Firestore's auto-IDs are designed to be unique and efficient for scaling.

```
import { db } from "@/lib/firebase";
import { collection, addDoc, serverTimestamp } from "firebase/firestore";

async function createOrder(orderData: {
  product: string;
  quantity: number;
  userId: string;
}) {
  try {
    const docRef = await addDoc(collection(db, "orders"), {
      ...orderData,
      status: "pending",
      createdAt: serverTimestamp(),
    });

    console.log("Document created with ID:", docRef.id);
    return docRef.id;
  } catch (error) {
    console.error("Error adding document:", error);
    throw error;
  }
}
```

**Expected result:** A new document is created in the orders collection with a unique auto-generated ID.

### 2. Add a document with a custom ID using setDoc

Use setDoc() when you want to control the document ID — for example, using a user's UID as the document ID for their profile. Pass a document reference (which includes the collection and ID) and the data. Note that setDoc overwrites the entire document if it already exists, unless you pass the merge option.

```
import { db } from "@/lib/firebase";
import { doc, setDoc, serverTimestamp } from "firebase/firestore";

async function createUserProfile(
  userId: string,
  profile: { displayName: string; email: string }
) {
  try {
    await setDoc(doc(db, "users", userId), {
      ...profile,
      createdAt: serverTimestamp(),
      role: "member",
    });

    console.log("User profile created for:", userId);
  } catch (error) {
    console.error("Error creating profile:", error);
    throw error;
  }
}
```

**Expected result:** A document with the specified userId as its ID is created in the users collection.

### 3. Add a document to a subcollection

Firestore supports nested collections (subcollections) inside documents. To add a document to a subcollection, build the full collection path including the parent document ID. Subcollections are great for data scoped to a parent, like messages inside a chat room or items inside an order.

```
import { db } from "@/lib/firebase";
import { collection, addDoc, serverTimestamp } from "firebase/firestore";

async function addMessage(chatRoomId: string, message: {
  text: string;
  senderId: string;
}) {
  const messagesRef = collection(db, "chatRooms", chatRoomId, "messages");

  const docRef = await addDoc(messagesRef, {
    ...message,
    sentAt: serverTimestamp(),
  });

  return docRef.id;
}
```

**Expected result:** A new message document is created inside the messages subcollection of the specified chat room.

### 4. Handle supported data types

Firestore supports strings, numbers, booleans, null, arrays, nested objects (maps), timestamps, geopoints, and document references. Understanding these types helps you structure your documents correctly. Nested objects can be up to 20 levels deep, and the total document size limit is 1 MiB.

```
import { db } from "@/lib/firebase";
import {
  collection, addDoc, serverTimestamp, GeoPoint, Timestamp
} from "firebase/firestore";

const eventDoc = await addDoc(collection(db, "events"), {
  name: "Firebase Meetup",            // string
  attendees: 42,                       // number
  isPublic: true,                      // boolean
  description: null,                   // null
  tags: ["firebase", "cloud"],         // array
  location: {                          // nested object (map)
    city: "San Francisco",
    state: "CA",
  },
  coordinates: new GeoPoint(37.7749, -122.4194),  // geopoint
  eventDate: Timestamp.fromDate(new Date("2026-06-15")),  // timestamp
  createdAt: serverTimestamp(),        // server timestamp
});
```

**Expected result:** A document with various data types is created, and all types are stored correctly in Firestore.

### 5. Configure security rules for document creation

Firestore security rules control who can create documents. The default rules deny all access, so you must update them to allow writes. The create permission (a subset of write) specifically controls document creation. Always validate incoming data in your rules to prevent malformed documents.

```
// firestore.rules
rules_version = '2';
service cloud.firestore {
  match /databases/{database}/documents {

    // Users can create their own profile
    match /users/{userId} {
      allow create: if request.auth != null
        && request.auth.uid == userId
        && request.resource.data.displayName is string
        && request.resource.data.displayName.size() > 0;
      allow read: if request.auth != null;
    }

    // Authenticated users can create orders
    match /orders/{orderId} {
      allow create: if request.auth != null
        && request.resource.data.userId == request.auth.uid
        && request.resource.data.product is string;
      allow read: if request.auth != null
        && resource.data.userId == request.auth.uid;
    }
  }
}
```

**Expected result:** Security rules are deployed, and only authenticated users can create documents with validated data.

## Complete code example

File: `src/lib/firestore-create.ts`

```typescript
import { db } from "@/lib/firebase";
import {
  collection,
  addDoc,
  doc,
  setDoc,
  serverTimestamp,
} from "firebase/firestore";

// Add document with auto-generated ID
export async function createOrder(order: {
  product: string;
  quantity: number;
  userId: string;
}) {
  const docRef = await addDoc(collection(db, "orders"), {
    ...order,
    status: "pending",
    createdAt: serverTimestamp(),
  });
  return docRef.id;
}

// Add document with custom ID
export async function createUserProfile(
  userId: string,
  profile: { displayName: string; email: string }
) {
  await setDoc(doc(db, "users", userId), {
    ...profile,
    role: "member",
    createdAt: serverTimestamp(),
  });
}

// Add to a subcollection
export async function addOrderItem(
  orderId: string,
  item: { name: string; price: number; quantity: number }
) {
  const itemsRef = collection(db, "orders", orderId, "items");
  const docRef = await addDoc(itemsRef, {
    ...item,
    addedAt: serverTimestamp(),
  });
  return docRef.id;
}

// Upsert — create or merge into existing document
export async function upsertUserSettings(
  userId: string,
  settings: Record<string, unknown>
) {
  await setDoc(
    doc(db, "userSettings", userId),
    {
      ...settings,
      updatedAt: serverTimestamp(),
    },
    { merge: true }
  );
}
```

## Common mistakes

- **Using setDoc without realizing it overwrites the entire document, deleting fields not included in the new data** — undefined Fix: Pass { merge: true } as the third argument to setDoc to merge new fields with existing data: setDoc(docRef, data, { merge: true }). Or use updateDoc() for partial updates on existing documents.
- **Getting 'Missing or insufficient permissions' error because security rules have not been configured for create operations** — undefined Fix: Update your Firestore security rules to allow create for the target collection. Deploy with firebase deploy --only firestore:rules. For testing, you can temporarily use allow write: if request.auth != null, but always add data validation for production.
- **Using new Date() instead of serverTimestamp() for document timestamps** — undefined Fix: Use serverTimestamp() from firebase/firestore to let the Firestore server set the timestamp. Client-side Date() depends on the user's device clock, which may be inaccurate.

## Best practices

- Use addDoc() for most new documents and let Firestore generate unique IDs — only use setDoc() when you have a meaningful custom ID like a user UID
- Always use serverTimestamp() for createdAt and updatedAt fields to ensure consistency
- Wrap all write operations in try/catch blocks and display meaningful errors to users
- Validate data in security rules (check types, required fields, string lengths) to prevent malformed documents
- Use setDoc with { merge: true } for upsert patterns where you want to create or update a document
- Keep documents under 1 MiB and avoid deeply nested objects beyond 3-4 levels for query performance

## Frequently asked questions

### What is the difference between addDoc and setDoc?

addDoc creates a document with a Firestore-generated unique ID and returns a DocumentReference. setDoc creates (or overwrites) a document at a specific path with an ID you choose. Use addDoc for most cases; use setDoc when you need a meaningful ID like a user UID.

### Does setDoc overwrite existing documents?

Yes, by default setDoc completely replaces the document at the given path. To merge instead of overwrite, pass { merge: true } as the third argument: setDoc(ref, data, { merge: true }).

### Why do I get 'Missing or insufficient permissions' when adding a document?

Your Firestore security rules do not allow the create operation for the target collection. Update your rules to include allow create with appropriate conditions (like requiring authentication) and deploy with firebase deploy --only firestore:rules.

### Can I add a document without being authenticated?

Only if your security rules allow it with allow create: if true, which is not recommended for production. In production, always require authentication (request.auth != null) and validate the incoming data.

### What data types does Firestore support?

Firestore supports strings, numbers, booleans, null, arrays, maps (nested objects), timestamps (Timestamp or serverTimestamp), geopoints (GeoPoint), and document references. It does not support undefined — omit the field or use null instead.

### Is there a size limit for Firestore documents?

Yes. The maximum document size is 1 MiB (approximately 1 million bytes) with a limit of 20,000 fields per document. For larger data, consider splitting across multiple documents or using Cloud Storage for files.

### How do I add a document to a subcollection?

Build the collection reference with the full path: collection(db, 'parentCollection', 'parentDocId', 'subcollection'). Then use addDoc or setDoc as normal. Subcollections are created automatically when you add the first document.

---

Source: https://www.rapidevelopers.com/firebase-tutorial/how-to-add-a-new-document-in-firestore
© RapidDev — https://www.rapidevelopers.com/firebase-tutorial/how-to-add-a-new-document-in-firestore
