To update data in Firebase Realtime Database, use the update() method on a database reference to merge new values into an existing node without overwriting sibling keys. For multi-path updates, pass an object with forward-slash paths to atomically update values at different locations in a single operation. This fan-out pattern is essential for denormalized data models where the same value lives in multiple places.
Updating Data in Firebase Realtime Database
Firebase Realtime Database stores data as a single JSON tree. Updating data correctly means choosing between set() which replaces the entire node and update() which merges only the specified keys. This tutorial covers partial updates, multi-path atomic updates (the fan-out pattern), server-side values like serverTimestamp and increment, and security rules that validate updates. These patterns are critical for keeping denormalized data consistent across your JSON tree.
Prerequisites
- A Firebase project with Realtime Database enabled
- Firebase JS SDK v9+ installed in your project
- Basic understanding of the Realtime Database JSON tree structure
- Data already written to the database (see the write data tutorial)
Step-by-step guide
Perform a partial update with update()
Perform a partial update with update()
The update() function merges the provided key-value pairs into the existing data at a reference path. Keys not included in the update object are left unchanged. This is different from set(), which replaces the entire node and deletes any keys not in the new data. Import update and ref from firebase/database.
1import { ref, update } from 'firebase/database';2import { db } from './firebase';34// Only updates displayName and email — other fields are preserved5async function updateUserProfile(userId: string) {6 await update(ref(db, `users/${userId}`), {7 displayName: 'Jane Doe',8 email: 'jane@example.com',9 updatedAt: Date.now(),10 });11}Expected result: The displayName, email, and updatedAt fields are updated while all other fields on the user node remain unchanged.
Use multi-path updates for the fan-out pattern
Use multi-path updates for the fan-out pattern
Multi-path updates let you atomically write to multiple locations in the database with a single operation. This is the fan-out pattern — when you need to update the same data in several places (like updating a user's name in their profile and in every post they authored). Pass an object where keys are forward-slash-separated paths from the root.
1import { ref, update } from 'firebase/database';23async function updatePostAndTimeline(4 postId: string,5 authorId: string,6 newTitle: string7) {8 const updates: Record<string, any> = {};910 // Update the post itself11 updates[`posts/${postId}/title`] = newTitle;12 updates[`posts/${postId}/updatedAt`] = Date.now();1314 // Update the post title in the author's timeline15 updates[`user-posts/${authorId}/${postId}/title`] = newTitle;1617 // Update the post title in the global feed18 updates[`feed/${postId}/title`] = newTitle;1920 // All three paths update atomically21 await update(ref(db), updates);22}Expected result: The post title is updated in all three locations (posts, user-posts, feed) in a single atomic operation.
Use serverTimestamp and increment for server-side values
Use serverTimestamp and increment for server-side values
Server-side values ensure consistency when multiple clients write simultaneously. serverTimestamp() writes the server's current time (not the client's clock), and increment() atomically adds to a number value without reading it first. Import these from firebase/database.
1import { ref, update, serverTimestamp, increment } from 'firebase/database';23async function recordPageView(pageId: string) {4 await update(ref(db, `pages/${pageId}`), {5 viewCount: increment(1),6 lastViewedAt: serverTimestamp(),7 });8}910async function decrementStock(productId: string, quantity: number) {11 await update(ref(db, `products/${productId}`), {12 stock: increment(-quantity),13 updatedAt: serverTimestamp(),14 });15}Expected result: The viewCount field is atomically incremented by 1 and lastViewedAt is set to the server's current timestamp.
Delete specific keys during an update
Delete specific keys during an update
To remove specific keys while updating others in the same operation, set the unwanted keys to null. In Realtime Database, writing null to a path deletes that path. This works in both regular updates and multi-path updates.
1import { ref, update } from 'firebase/database';23async function cleanUpUserProfile(userId: string) {4 await update(ref(db, `users/${userId}`), {5 // Update these fields6 displayName: 'Updated Name',7 updatedAt: Date.now(),8 // Delete these fields9 legacyField: null,10 tempData: null,11 });12}Expected result: The displayName and updatedAt fields are updated, while legacyField and tempData are completely removed from the node.
Write security rules for update operations
Write security rules for update operations
Realtime Database security rules use a JSON structure with .read, .write, and .validate rules. To restrict updates to the document owner, check auth.uid against the path. Use .validate rules to enforce data types and required fields on writes.
1// database.rules.json2{3 "rules": {4 "users": {5 "$userId": {6 ".read": "auth != null && auth.uid === $userId",7 ".write": "auth != null && auth.uid === $userId",8 "displayName": {9 ".validate": "newData.isString() && newData.val().length > 0 && newData.val().length <= 50"10 },11 "email": {12 ".validate": "newData.isString() && newData.val().matches(/^[^@]+@[^@]+$/)"13 },14 "role": {15 ".write": false16 }17 }18 }19 }20}Expected result: Users can only update their own profiles, displayName and email are validated, and the role field cannot be modified from the client.
Complete working example
1import {2 ref,3 update,4 set,5 serverTimestamp,6 increment,7} from 'firebase/database';8import { db } from './firebase';910// Partial update — merges keys into existing data11export async function updateFields(12 path: string,13 data: Record<string, any>14): Promise<void> {15 await update(ref(db, path), {16 ...data,17 updatedAt: serverTimestamp(),18 });19}2021// Full overwrite — replaces the entire node22export async function overwriteNode(23 path: string,24 data: Record<string, any>25): Promise<void> {26 await set(ref(db, path), {27 ...data,28 updatedAt: serverTimestamp(),29 });30}3132// Multi-path atomic update (fan-out pattern)33export async function fanOutUpdate(34 updates: Record<string, any>35): Promise<void> {36 await update(ref(db), updates);37}3839// Atomic counter increment40export async function incrementField(41 path: string,42 field: string,43 amount: number = 144): Promise<void> {45 await update(ref(db, path), {46 [field]: increment(amount),47 updatedAt: serverTimestamp(),48 });49}5051// Example: update post title across multiple locations52export async function updatePostTitle(53 postId: string,54 authorId: string,55 newTitle: string56): Promise<void> {57 const updates: Record<string, any> = {58 [`posts/${postId}/title`]: newTitle,59 [`posts/${postId}/updatedAt`]: serverTimestamp(),60 [`user-posts/${authorId}/${postId}/title`]: newTitle,61 [`feed/${postId}/title`]: newTitle,62 };63 await fanOutUpdate(updates);64}Common mistakes when updating Data in Firebase Realtime Database
Why it's a problem: Using set() instead of update() and accidentally overwriting the entire node including unspecified fields
How to avoid: Use update() for partial merges. Only use set() when you intentionally want to replace the entire node with new data.
Why it's a problem: Expecting update() to deep-merge nested objects when it only does a shallow merge
How to avoid: Use forward-slash paths for nested fields: { 'address/city': 'NYC' } instead of { address: { city: 'NYC' } } to avoid overwriting sibling keys.
Why it's a problem: Not using multi-path updates for denormalized data, leading to inconsistent copies
How to avoid: When the same data lives in multiple paths, always use a single multi-path update to change all copies atomically.
Best practices
- Use update() for partial modifications and set() only when you want to replace the entire node
- Use multi-path updates (fan-out) to keep denormalized data consistent across the JSON tree
- Always include a serverTimestamp() field like updatedAt to track when nodes were last modified
- Use increment() for counters instead of reading, modifying, and writing — it avoids race conditions
- Write .validate rules to enforce data types and value ranges on every writable field
- Set specific child paths to .write: false to make fields read-only even when the parent is writable
- Delete fields by setting them to null within an update operation instead of using remove() separately
Still stuck?
Copy one of these prompts to get a personalized, step-by-step explanation.
Show me how to update data in Firebase Realtime Database using the v9 modular SDK. Include update() for partial merges, multi-path fan-out updates, serverTimestamp, increment, deleting keys by setting null, and security rules for validating updates.
Write TypeScript utility functions for Firebase Realtime Database updates: partial update with update(), full overwrite with set(), multi-path fan-out pattern, atomic increment and serverTimestamp. Use Firebase modular SDK v9+ imports from 'firebase/database'.
Frequently asked questions
What is the difference between update() and set() in Realtime Database?
update() merges the specified keys into the existing node without touching other keys. set() replaces the entire node with the new data, deleting any keys not in the new object. Use update() for partial modifications and set() for full overwrites.
Are multi-path updates atomic?
Yes. A multi-path update either writes all paths successfully or writes none. If any path fails a security rule check, the entire operation is rejected.
Can I update data while the device is offline?
Yes. With persistence enabled (default on mobile), updates are stored locally and synced when the device reconnects. Local listeners see the update immediately.
How do I update a deeply nested field without overwriting siblings?
Use forward-slash paths in the update object: update(ref(db, 'users/uid'), { 'settings/notifications/email': true }). This updates only the email key without affecting other notification settings.
Is there a limit on how many paths I can update at once?
There is no documented hard limit on the number of paths in a multi-path update, but the total payload size must be under 16 MB. In practice, keep multi-path updates to a few hundred paths for best performance.
Can RapidDev help design my Realtime Database schema and update patterns?
Yes. RapidDev can help you design efficient denormalized JSON structures, implement fan-out update patterns, write security rules, and optimize for real-time performance.
Talk to an Expert
Our team has built 600+ apps. Get personalized help with your project.
Book a free consultation