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
Add a document with an auto-generated ID using addDoc
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.
1import { db } from "@/lib/firebase";2import { collection, addDoc, serverTimestamp } from "firebase/firestore";34async 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 });1516 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.
Add a document with a custom ID using setDoc
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.
1import { db } from "@/lib/firebase";2import { doc, setDoc, serverTimestamp } from "firebase/firestore";34async 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 });1415 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.
Add a document to a subcollection
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.
1import { db } from "@/lib/firebase";2import { collection, addDoc, serverTimestamp } from "firebase/firestore";34async function addMessage(chatRoomId: string, message: {5 text: string;6 senderId: string;7}) {8 const messagesRef = collection(db, "chatRooms", chatRoomId, "messages");910 const docRef = await addDoc(messagesRef, {11 ...message,12 sentAt: serverTimestamp(),13 });1415 return docRef.id;16}Expected result: A new message document is created inside the messages subcollection of the specified chat room.
Handle supported data types
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.
1import { db } from "@/lib/firebase";2import {3 collection, addDoc, serverTimestamp, GeoPoint, Timestamp4} from "firebase/firestore";56const eventDoc = await addDoc(collection(db, "events"), {7 name: "Firebase Meetup", // string8 attendees: 42, // number9 isPublic: true, // boolean10 description: null, // null11 tags: ["firebase", "cloud"], // array12 location: { // nested object (map)13 city: "San Francisco",14 state: "CA",15 },16 coordinates: new GeoPoint(37.7749, -122.4194), // geopoint17 eventDate: Timestamp.fromDate(new Date("2026-06-15")), // timestamp18 createdAt: serverTimestamp(), // server timestamp19});Expected result: A document with various data types is created, and all types are stored correctly in Firestore.
Configure security rules for document creation
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.
1// firestore.rules2rules_version = '2';3service cloud.firestore {4 match /databases/{database}/documents {56 // Users can create their own profile7 match /users/{userId} {8 allow create: if request.auth != null9 && request.auth.uid == userId10 && request.resource.data.displayName is string11 && request.resource.data.displayName.size() > 0;12 allow read: if request.auth != null;13 }1415 // Authenticated users can create orders16 match /orders/{orderId} {17 allow create: if request.auth != null18 && request.resource.data.userId == request.auth.uid19 && request.resource.data.product is string;20 allow read: if request.auth != null21 && 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
1import { db } from "@/lib/firebase";2import {3 collection,4 addDoc,5 doc,6 setDoc,7 serverTimestamp,8} from "firebase/firestore";910// Add document with auto-generated ID11export 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}2324// Add document with custom ID25export 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}3536// Add to a subcollection37export 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}4849// Upsert — create or merge into existing document50export 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.
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.
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.
Talk to an Expert
Our team has built 600+ apps. Get personalized help with your project.
Book a free consultation