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

How to Update a Document in Firestore

To update a document in Firestore, use updateDoc() for partial field updates or setDoc() with merge: true for upsert behavior. The modular SDK v9+ provides atomic field modifiers like increment(), arrayUnion(), arrayRemove(), and serverTimestamp() that let you modify specific values without reading the document first. Always ensure security rules permit update operations for authenticated users.

What you'll learn

  • How to use updateDoc() for partial updates and setDoc() with merge for upserts
  • How to use atomic field modifiers like increment(), arrayUnion(), and serverTimestamp()
  • How to update nested fields using dot notation without overwriting sibling fields
  • How to write security rules that authorize update operations
Book a free consultation
4.9Clutch rating
600+Happy partners
17+Countries served
190+Team members
Intermediate8 min read12-18 minFirebase JS SDK v9+, Firestore on all Firebase plansMarch 2026RapidDev Engineering Team
TL;DR

To update a document in Firestore, use updateDoc() for partial field updates or setDoc() with merge: true for upsert behavior. The modular SDK v9+ provides atomic field modifiers like increment(), arrayUnion(), arrayRemove(), and serverTimestamp() that let you modify specific values without reading the document first. Always ensure security rules permit update operations for authenticated users.

Updating Documents in Firestore with the Modular SDK

Firestore supports two approaches to modifying existing documents: updateDoc() for partial updates (fails if the document does not exist) and setDoc() with merge: true for upsert behavior (creates the document if missing). This tutorial covers both methods, shows you how to use atomic modifiers for counters and arrays, explains nested field updates with dot notation, and includes security rules that validate update operations.

Prerequisites

  • A Firebase project with Firestore database created
  • Firebase JS SDK v9+ installed in your project
  • At least one document in a Firestore collection to update
  • Basic understanding of JavaScript or TypeScript

Step-by-step guide

1

Update specific fields with updateDoc()

The updateDoc() function modifies only the fields you specify, leaving all other fields unchanged. It requires a document reference and an object of field-value pairs. If the document does not exist, updateDoc() throws a 'not-found' error. Use this when you know the document already exists and you want to change specific fields.

typescript
1import { doc, updateDoc } from 'firebase/firestore';
2import { db } from './firebase';
3
4async function updateUserName(userId: string, newName: string) {
5 const userRef = doc(db, 'users', userId);
6
7 await updateDoc(userRef, {
8 displayName: newName,
9 updatedAt: new Date(),
10 });
11
12 console.log('User name updated successfully');
13}

Expected result: The displayName and updatedAt fields are updated, and all other fields on the document remain unchanged.

2

Upsert with setDoc() and merge option

Use setDoc() with { merge: true } when you want to update a document if it exists or create it if it does not. This is useful for idempotent operations where you do not want to check existence first. Without the merge option, setDoc() overwrites the entire document, deleting any fields not included in the new data.

typescript
1import { doc, setDoc } from 'firebase/firestore';
2
3async function upsertUserProfile(userId: string, data: Record<string, any>) {
4 const userRef = doc(db, 'users', userId);
5
6 // Creates the document if it doesn't exist,
7 // or merges fields if it does
8 await setDoc(userRef, {
9 ...data,
10 lastSeen: new Date(),
11 }, { merge: true });
12}

Expected result: The document is created with the provided data if new, or the specified fields are merged into the existing document.

3

Use atomic modifiers for counters and arrays

Firestore provides special field values that perform atomic operations server-side. increment() adds or subtracts from a number field without reading the current value. arrayUnion() adds elements to an array only if they are not already present. arrayRemove() removes all instances of specified elements. serverTimestamp() writes the server's current time, ensuring consistency.

typescript
1import { doc, updateDoc, increment, arrayUnion, arrayRemove, serverTimestamp } from 'firebase/firestore';
2
3// Increment a counter without reading current value
4await updateDoc(doc(db, 'posts', 'post123'), {
5 viewCount: increment(1),
6 lastViewed: serverTimestamp(),
7});
8
9// Add a tag to an array (only if not already present)
10await updateDoc(doc(db, 'posts', 'post123'), {
11 tags: arrayUnion('firebase'),
12});
13
14// Remove a tag from an array
15await updateDoc(doc(db, 'posts', 'post123'), {
16 tags: arrayRemove('deprecated'),
17});

Expected result: Fields are modified atomically on the server without needing to read the current document value first.

4

Update nested fields with dot notation

When a document has nested objects, use dot-separated field paths to update specific nested fields without overwriting the entire parent object. Without dot notation, passing a nested object to updateDoc() replaces the entire nested object, removing any sibling fields you did not include.

typescript
1import { doc, updateDoc } from 'firebase/firestore';
2
3// Document structure: { address: { city: 'NYC', state: 'NY', zip: '10001' } }
4
5// CORRECT: Update only the city, keep state and zip
6await updateDoc(doc(db, 'users', 'user123'), {
7 'address.city': 'Los Angeles',
8 'address.state': 'CA',
9});
10
11// WRONG: This replaces the entire address object, deleting zip
12// await updateDoc(doc(db, 'users', 'user123'), {
13// address: { city: 'Los Angeles', state: 'CA' },
14// });

Expected result: Only the specified nested fields are updated while sibling fields in the same nested object remain untouched.

5

Write security rules for update operations

Firestore security rules let you control who can update documents and which fields they can modify. The request.resource.data object contains the document as it would look after the update. Compare it with resource.data (the current document) to validate specific changes. You can restrict updates to the document owner and validate that certain fields are not modified.

typescript
1// firestore.rules
2rules_version = '2';
3service cloud.firestore {
4 match /databases/{database}/documents {
5 match /users/{userId} {
6 // Only the user can update their own document
7 allow update: if request.auth != null
8 && request.auth.uid == userId
9 // Prevent users from changing their role field
10 && request.resource.data.role == resource.data.role;
11 }
12
13 match /posts/{postId} {
14 // Only the author can update their post
15 allow update: if request.auth != null
16 && request.auth.uid == resource.data.authorId;
17 }
18 }
19}

Expected result: Only authorized users can update documents, and protected fields like role cannot be changed by the client.

6

Handle update errors gracefully

Firestore update operations can fail for several reasons: the document does not exist (updateDoc only), security rules deny the operation, or the client is offline without persistence enabled. Always wrap updates in try-catch blocks and provide meaningful error messages to the user.

typescript
1import { doc, updateDoc, FirestoreError } from 'firebase/firestore';
2
3async function safeUpdate(
4 collectionName: string,
5 docId: string,
6 data: Record<string, any>
7): Promise<{ success: boolean; error?: string }> {
8 try {
9 await updateDoc(doc(db, collectionName, docId), data);
10 return { success: true };
11 } catch (err) {
12 const error = err as FirestoreError;
13 if (error.code === 'not-found') {
14 return { success: false, error: 'Document does not exist.' };
15 }
16 if (error.code === 'permission-denied') {
17 return { success: false, error: 'You do not have permission to update this document.' };
18 }
19 return { success: false, error: error.message };
20 }
21}

Expected result: Errors are caught and returned as structured objects instead of crashing the application.

Complete working example

firestore-update.ts
1import {
2 doc,
3 updateDoc,
4 setDoc,
5 increment,
6 arrayUnion,
7 arrayRemove,
8 serverTimestamp,
9 FirestoreError,
10} from 'firebase/firestore';
11import { db } from './firebase';
12
13// Partial update — fails if document does not exist
14export async function updateFields(
15 path: string,
16 id: string,
17 data: Record<string, any>
18) {
19 const ref = doc(db, path, id);
20 await updateDoc(ref, { ...data, updatedAt: serverTimestamp() });
21}
22
23// Upsert — creates or merges
24export async function upsertDocument(
25 path: string,
26 id: string,
27 data: Record<string, any>
28) {
29 const ref = doc(db, path, id);
30 await setDoc(ref, { ...data, updatedAt: serverTimestamp() }, { merge: true });
31}
32
33// Atomic counter increment
34export async function incrementCounter(
35 path: string,
36 id: string,
37 field: string,
38 amount: number = 1
39) {
40 await updateDoc(doc(db, path, id), { [field]: increment(amount) });
41}
42
43// Atomic array operations
44export async function addToArray(
45 path: string,
46 id: string,
47 field: string,
48 values: any[]
49) {
50 await updateDoc(doc(db, path, id), { [field]: arrayUnion(...values) });
51}
52
53export async function removeFromArray(
54 path: string,
55 id: string,
56 field: string,
57 values: any[]
58) {
59 await updateDoc(doc(db, path, id), { [field]: arrayRemove(...values) });
60}
61
62// Safe update with error handling
63export async function safeUpdate(
64 path: string,
65 id: string,
66 data: Record<string, any>
67): Promise<{ success: boolean; error?: string }> {
68 try {
69 await updateDoc(doc(db, path, id), {
70 ...data,
71 updatedAt: serverTimestamp(),
72 });
73 return { success: true };
74 } catch (err) {
75 const e = err as FirestoreError;
76 return { success: false, error: e.message };
77 }
78}

Common mistakes when updating a Document in Firestore

Why it's a problem: Using setDoc() without { merge: true } and accidentally overwriting the entire document

How to avoid: Always pass { merge: true } as the third argument to setDoc() when you only want to update specific fields: setDoc(ref, data, { merge: true }).

Why it's a problem: Passing a nested object to updateDoc() instead of using dot notation, which replaces the entire nested object

How to avoid: Use dot-separated paths for nested updates: { 'address.city': 'NYC' } instead of { address: { city: 'NYC' } }.

Why it's a problem: Calling updateDoc() on a document that does not exist, causing a not-found error

How to avoid: Use setDoc() with merge: true if the document may not exist. Or check existence first with getDoc() before calling updateDoc().

Why it's a problem: Reading a document first to increment a counter instead of using the atomic increment() function

How to avoid: Use increment(1) which runs atomically on the server. Reading, modifying, and writing creates a race condition in concurrent environments.

Best practices

  • Use updateDoc() when you know the document exists and want a partial update; use setDoc with merge for upserts
  • Always add a serverTimestamp() field like updatedAt so you can track when documents were last modified
  • Use dot notation for nested field updates to avoid accidentally overwriting sibling fields
  • Prefer atomic modifiers (increment, arrayUnion, arrayRemove) over read-then-write patterns to avoid race conditions
  • Write security rules that validate which fields can be changed and who can change them
  • Wrap all update calls in try-catch to handle not-found and permission-denied errors gracefully
  • Batch multiple updates together with writeBatch() when modifying several documents atomically

Still stuck?

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

ChatGPT Prompt

Show me how to update Firestore documents using the Firebase v9 modular SDK. Include updateDoc for partial updates, setDoc with merge for upserts, atomic increment, arrayUnion, arrayRemove, serverTimestamp, nested field updates with dot notation, and security rules.

Firebase Prompt

Write TypeScript utility functions for Firestore document updates: partial updates with updateDoc, upserts with setDoc merge, atomic counter increment, array operations with arrayUnion/arrayRemove, and nested field dot notation. Use Firebase modular SDK v9+ imports.

Frequently asked questions

What is the difference between updateDoc and setDoc with merge?

updateDoc() fails with a not-found error if the document does not exist. setDoc() with { merge: true } creates the document if it is missing and merges fields if it exists. Use updateDoc when you expect the document to exist; use setDoc merge for upsert behavior.

Can I update a field inside a map without overwriting other map fields?

Yes. Use dot notation: updateDoc(ref, { 'address.city': 'NYC' }). This updates only the city field inside the address map without affecting other fields like state or zip.

Does updateDoc trigger onSnapshot listeners?

Yes. Any change made with updateDoc or setDoc triggers all active onSnapshot listeners on that document or any query that includes it. Listeners receive the updated data in real time.

Can I update multiple documents at once?

Use writeBatch() to update up to 500 documents atomically in a single operation. For more than 500 documents, split them into multiple batches.

What happens if I update a document while offline?

With offline persistence enabled (default on mobile, opt-in on web), the update is stored locally and synced to the server when the device reconnects. Local listeners see the update immediately.

How do I remove a single field from a document?

Import deleteField() and pass it as the value: updateDoc(ref, { fieldToRemove: deleteField() }). This removes the field entirely from the document.

Can RapidDev help design my Firestore data model and update patterns?

Yes. RapidDev can help you design efficient Firestore schemas, implement atomic update patterns, write security rules, and optimize for cost and performance.

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.