Skip to main content
RapidDev - Software Development Agency
firebase-tutorial

How to Add a New Document in Firestore

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.

What you'll learn

  • How to use addDoc() to create documents with auto-generated IDs
  • How to use setDoc() to create documents with custom IDs
  • How to handle the returned DocumentReference and error states
  • How to write security rules that allow document creation
Book a free consultation
4.9Clutch rating
600+Happy partners
17+Countries served
190+Team members
Beginner8 min read10-15 minFirebase (all plans), firebase 10.x+, Firestore modular SDK v9+March 2026RapidDev Engineering Team
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.

Prerequisites

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

typescript
1import { db } from "@/lib/firebase";
2import { collection, addDoc, serverTimestamp } from "firebase/firestore";
3
4async function createOrder(orderData: {
5 product: string;
6 quantity: number;
7 userId: string;
8}) {
9 try {
10 const docRef = await addDoc(collection(db, "orders"), {
11 ...orderData,
12 status: "pending",
13 createdAt: serverTimestamp(),
14 });
15
16 console.log("Document created with ID:", docRef.id);
17 return docRef.id;
18 } catch (error) {
19 console.error("Error adding document:", error);
20 throw error;
21 }
22}

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.

typescript
1import { db } from "@/lib/firebase";
2import { doc, setDoc, serverTimestamp } from "firebase/firestore";
3
4async function createUserProfile(
5 userId: string,
6 profile: { displayName: string; email: string }
7) {
8 try {
9 await setDoc(doc(db, "users", userId), {
10 ...profile,
11 createdAt: serverTimestamp(),
12 role: "member",
13 });
14
15 console.log("User profile created for:", userId);
16 } catch (error) {
17 console.error("Error creating profile:", error);
18 throw error;
19 }
20}

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.

typescript
1import { db } from "@/lib/firebase";
2import { collection, addDoc, serverTimestamp } from "firebase/firestore";
3
4async function addMessage(chatRoomId: string, message: {
5 text: string;
6 senderId: string;
7}) {
8 const messagesRef = collection(db, "chatRooms", chatRoomId, "messages");
9
10 const docRef = await addDoc(messagesRef, {
11 ...message,
12 sentAt: serverTimestamp(),
13 });
14
15 return docRef.id;
16}

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.

typescript
1import { db } from "@/lib/firebase";
2import {
3 collection, addDoc, serverTimestamp, GeoPoint, Timestamp
4} from "firebase/firestore";
5
6const eventDoc = await addDoc(collection(db, "events"), {
7 name: "Firebase Meetup", // string
8 attendees: 42, // number
9 isPublic: true, // boolean
10 description: null, // null
11 tags: ["firebase", "cloud"], // array
12 location: { // nested object (map)
13 city: "San Francisco",
14 state: "CA",
15 },
16 coordinates: new GeoPoint(37.7749, -122.4194), // geopoint
17 eventDate: Timestamp.fromDate(new Date("2026-06-15")), // timestamp
18 createdAt: serverTimestamp(), // server timestamp
19});

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.

typescript
1// firestore.rules
2rules_version = '2';
3service cloud.firestore {
4 match /databases/{database}/documents {
5
6 // Users can create their own profile
7 match /users/{userId} {
8 allow create: if request.auth != null
9 && request.auth.uid == userId
10 && request.resource.data.displayName is string
11 && request.resource.data.displayName.size() > 0;
12 allow read: if request.auth != null;
13 }
14
15 // Authenticated users can create orders
16 match /orders/{orderId} {
17 allow create: if request.auth != null
18 && request.resource.data.userId == request.auth.uid
19 && request.resource.data.product is string;
20 allow read: if request.auth != null
21 && resource.data.userId == request.auth.uid;
22 }
23 }
24}

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

Complete working example

src/lib/firestore-create.ts
1import { db } from "@/lib/firebase";
2import {
3 collection,
4 addDoc,
5 doc,
6 setDoc,
7 serverTimestamp,
8} from "firebase/firestore";
9
10// Add document with auto-generated ID
11export async function createOrder(order: {
12 product: string;
13 quantity: number;
14 userId: string;
15}) {
16 const docRef = await addDoc(collection(db, "orders"), {
17 ...order,
18 status: "pending",
19 createdAt: serverTimestamp(),
20 });
21 return docRef.id;
22}
23
24// Add document with custom ID
25export async function createUserProfile(
26 userId: string,
27 profile: { displayName: string; email: string }
28) {
29 await setDoc(doc(db, "users", userId), {
30 ...profile,
31 role: "member",
32 createdAt: serverTimestamp(),
33 });
34}
35
36// Add to a subcollection
37export async function addOrderItem(
38 orderId: string,
39 item: { name: string; price: number; quantity: number }
40) {
41 const itemsRef = collection(db, "orders", orderId, "items");
42 const docRef = await addDoc(itemsRef, {
43 ...item,
44 addedAt: serverTimestamp(),
45 });
46 return docRef.id;
47}
48
49// Upsert — create or merge into existing document
50export async function upsertUserSettings(
51 userId: string,
52 settings: Record<string, unknown>
53) {
54 await setDoc(
55 doc(db, "userSettings", userId),
56 {
57 ...settings,
58 updatedAt: serverTimestamp(),
59 },
60 { merge: true }
61 );
62}

Common mistakes when adding a New Document in Firestore

Why it's a problem: Using setDoc without realizing it overwrites the entire document, deleting fields not included in the new data

How to avoid: 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.

Why it's a problem: Getting 'Missing or insufficient permissions' error because security rules have not been configured for create operations

How to avoid: 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.

Why it's a problem: Using new Date() instead of serverTimestamp() for document timestamps

How to avoid: 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

Still stuck?

Copy one of these prompts to get a personalized, step-by-step explanation.

ChatGPT Prompt

Show me how to add a document to Firestore using the modular v9+ SDK in TypeScript. Use addDoc for auto-generated IDs and setDoc for custom IDs. Include serverTimestamp, error handling, and the matching Firestore security rules for create permission.

Firebase Prompt

Add a new document to the orders collection in Firestore using addDoc from firebase/firestore with the modular v9 SDK. Include product, quantity, userId, status, and createdAt fields with serverTimestamp(). Show the Firestore security rules that allow authenticated users to create orders only with their own userId.

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.

RapidDev

Talk to an Expert

Our team has built 600+ apps. Get personalized help with your project.

Book a free consultation

Need help with your project?

Our experts have built 600+ apps and can accelerate your development. Book a free consultation — no strings attached.

Book a free consultation

We put the rapid in RapidDev

Need a dedicated strategic tech and growth partner? Discover what RapidDev can do for your business! Book a call with our team to schedule a free, no-obligation consultation. We'll discuss your project and provide a custom quote at no cost.