Firebase Realtime Database offers three write methods: set() overwrites an entire path, update() merges specific fields without touching others, and push() creates a new child node with an auto-generated key. Use serverTimestamp() for server-generated timestamps. All writes are atomic at their target path, and security rules control who can write where. Import ref, set, update, push, and serverTimestamp from firebase/database in the modular v9+ SDK.
Writing Data to Firebase Realtime Database
The Firebase Realtime Database stores data as a single JSON tree. This tutorial covers the three core write operations: set (overwrite), update (merge), and push (auto-ID). You will learn when to use each method, how to write to multiple paths atomically, and how to set up security rules that validate your data structure.
Prerequisites
- A Firebase project with Realtime Database created (Firebase Console > Realtime Database > Create Database)
- Firebase JS SDK v9+ installed in your project
- Firebase initialized with your project config including databaseURL
- Basic understanding of JSON data structures
Step-by-step guide
Write data with set to overwrite a path
Write data with set to overwrite a path
The set function writes data to a specific database reference path. It replaces everything at that path, including any child nodes. Use set when you want to create a new record or completely replace an existing one. Import ref and set from firebase/database.
1import { getDatabase, ref, set } from 'firebase/database'23const db = getDatabase()45// Create or overwrite a user profile6async function createUserProfile(userId: string, name: string, email: string) {7 await set(ref(db, `users/${userId}`), {8 name,9 email,10 createdAt: Date.now(),11 role: 'member'12 })13 console.log('User profile created')14}Expected result: The data at the specified path is completely replaced with the new object.
Merge data with update to modify specific fields
Merge data with update to modify specific fields
The update function merges the provided fields into the existing data at a path without overwriting fields you do not include. This is the safest way to modify specific fields. You can also use update with multiple paths to write atomically to different locations in the database.
1import { getDatabase, ref, update, serverTimestamp } from 'firebase/database'23const db = getDatabase()45// Update specific fields without overwriting others6async function updateUserProfile(userId: string, updates: Record<string, any>) {7 await update(ref(db, `users/${userId}`), {8 ...updates,9 updatedAt: serverTimestamp()10 })11}1213// Multi-path update (atomic write to multiple locations)14async function publishPost(postId: string, userId: string, title: string) {15 const postData = {16 title,17 authorId: userId,18 publishedAt: serverTimestamp()19 }2021 const updates: Record<string, any> = {}22 updates[`posts/${postId}`] = postData23 updates[`user-posts/${userId}/${postId}`] = postData24 updates[`users/${userId}/postCount`] = { '.sv': { 'increment': 1 } }2526 await update(ref(db), updates)27}Expected result: Only the specified fields are modified. Other existing fields at the path remain unchanged.
Generate unique keys with push
Generate unique keys with push
The push function creates a new child node with a unique, chronologically sorted key (like -NxYzAbCdEfGh). Use push for lists of items where you want Firebase to generate the ID. The key is based on a timestamp, so items pushed later sort after items pushed earlier.
1import { getDatabase, ref, push, set, serverTimestamp } from 'firebase/database'23const db = getDatabase()45// Add a new message to a chat6async function sendMessage(chatId: string, userId: string, text: string) {7 const messagesRef = ref(db, `chats/${chatId}/messages`)8 const newMessageRef = push(messagesRef)910 await set(newMessageRef, {11 text,12 senderId: userId,13 timestamp: serverTimestamp()14 })1516 console.log('Message sent with key:', newMessageRef.key)17 return newMessageRef.key18}Expected result: A new child node is created under the specified path with a unique auto-generated key.
Use serverTimestamp for accurate timestamps
Use serverTimestamp for accurate timestamps
Client-side Date.now() depends on the device clock, which can be inaccurate. Use serverTimestamp() to let the Firebase server set the timestamp at write time. The value is replaced with the server's Unix timestamp (milliseconds) when the write reaches the server.
1import { getDatabase, ref, set, serverTimestamp } from 'firebase/database'23const db = getDatabase()45async function logActivity(userId: string, action: string) {6 await set(ref(db, `activity/${userId}/latest`), {7 action,8 timestamp: serverTimestamp()9 })10}Expected result: The timestamp field is set to the Firebase server's time when the write is committed.
Write security rules to validate data
Write security rules to validate data
Security rules control who can write to each path and what the data must look like. Use .write to control access and .validate to check data types, required fields, and value ranges. Deploy rules with firebase deploy --only database.
1// database.rules.json2{3 "rules": {4 "users": {5 "$uid": {6 ".write": "auth != null && auth.uid == $uid",7 ".validate": "newData.hasChildren(['name', 'email'])",8 "name": {9 ".validate": "newData.isString() && newData.val().length > 0 && newData.val().length <= 100"10 },11 "email": {12 ".validate": "newData.isString() && newData.val().matches(/^[^@]+@[^@]+\\.[^@]+$/)"13 },14 "role": {15 ".validate": "newData.val() == 'member' || newData.val() == 'admin'"16 }17 }18 },19 "chats": {20 "$chatId": {21 "messages": {22 "$messageId": {23 ".write": "auth != null",24 ".validate": "newData.hasChildren(['text', 'senderId', 'timestamp'])",25 "text": {26 ".validate": "newData.isString() && newData.val().length > 0 && newData.val().length <= 1000"27 },28 "senderId": {29 ".validate": "newData.val() == auth.uid"30 }31 }32 }33 }34 }35 }36}Expected result: Writes that do not match the validation rules are rejected by Firebase with a PERMISSION_DENIED error.
Complete working example
1// src/lib/rtdb-write.ts2import {3 getDatabase,4 ref,5 set,6 update,7 push,8 remove,9 serverTimestamp10} from 'firebase/database'1112const db = getDatabase()1314// Create or overwrite a user profile15export async function createUser(userId: string, name: string, email: string) {16 await set(ref(db, `users/${userId}`), {17 name,18 email,19 role: 'member',20 createdAt: serverTimestamp()21 })22}2324// Update specific fields on a user profile25export async function updateUser(26 userId: string,27 fields: Record<string, any>28) {29 await update(ref(db, `users/${userId}`), {30 ...fields,31 updatedAt: serverTimestamp()32 })33}3435// Add a message to a chat using push36export async function sendMessage(37 chatId: string,38 senderId: string,39 text: string40) {41 const messagesRef = ref(db, `chats/${chatId}/messages`)42 const newRef = push(messagesRef)43 await set(newRef, {44 text,45 senderId,46 timestamp: serverTimestamp()47 })48 return newRef.key!49}5051// Multi-path atomic update (fan-out pattern)52export async function publishPost(53 postId: string,54 authorId: string,55 title: string,56 body: string57) {58 const post = {59 title,60 body,61 authorId,62 publishedAt: serverTimestamp()63 }64 const updates: Record<string, any> = {}65 updates[`/posts/${postId}`] = post66 updates[`/user-posts/${authorId}/${postId}`] = post67 await update(ref(db), updates)68}6970// Delete data at a path71export async function deleteMessage(chatId: string, messageId: string) {72 await remove(ref(db, `chats/${chatId}/messages/${messageId}`))73}Common mistakes when writing Data in Firebase Realtime Database
Why it's a problem: Using set() when you want to update a few fields, accidentally deleting all other fields at that path
How to avoid: Use update() to merge specific fields without affecting existing data. Reserve set() for creating new records or intentionally replacing all data at a path.
Why it's a problem: Using client-side Date.now() for timestamps, leading to inconsistent ordering across devices with different clocks
How to avoid: Use serverTimestamp() from firebase/database to let the Firebase server set the timestamp. This ensures consistent ordering regardless of client clock accuracy.
Why it's a problem: Writing deeply nested data structures that make querying difficult later
How to avoid: Keep your data structure flat. Use fan-out (multi-path updates) to denormalize data into query-friendly paths rather than nesting everything under a single parent.
Best practices
- Use update() by default for modifying existing data to avoid accidentally deleting fields
- Use push() for list data where you need unique, chronologically ordered keys
- Use serverTimestamp() instead of client-side Date.now() for consistent time ordering
- Use multi-path updates (fan-out) to keep denormalized copies in sync atomically
- Write security rules with .validate to enforce data structure and types on every write
- Keep the data structure flat and denormalized for efficient querying — avoid deep nesting
- For complex real-time data architectures with fan-out patterns across many paths, RapidDev can help design the data model and ensure consistency
Still stuck?
Copy one of these prompts to get a personalized, step-by-step explanation.
Show me the three ways to write data in Firebase Realtime Database: set, update, and push. Include examples of each, a multi-path atomic update using the fan-out pattern, serverTimestamp usage, and security rules that validate the data structure. Use Firebase modular SDK v9+ syntax.
Create a Realtime Database write utility module for a chat app. I need functions to create user profiles (set), update user settings (update), send messages with auto-generated keys (push), and publish posts with fan-out to multiple paths. Include serverTimestamp and security rules. Use Firebase modular SDK v9+.
Frequently asked questions
What is the difference between set and update in Realtime Database?
set() replaces all data at the specified path, including child nodes. update() merges the provided fields into the existing data without affecting fields you do not include. Use set for creating new records and update for modifying existing ones.
How does push generate unique keys?
push() generates keys based on a timestamp and random component, formatted as a string that sorts chronologically. Keys look like '-NxYzAbCdEfGh'. Items pushed later will sort after items pushed earlier when using orderByKey.
Can I write data when the app is offline?
Yes. Realtime Database queues writes locally when offline and syncs them when the connection is restored. This works for set, update, and push. However, serverTimestamp values are estimated locally and replaced with the actual server time on sync.
How do I delete data at a path?
Use remove(ref(db, 'path/to/data')) or set(ref(db, 'path/to/data'), null). Both completely remove the data and all children at that path.
What is the maximum data size I can write at once?
A single write operation can modify up to 16 MB of data. Individual string values are limited to 10 MB. For large data imports, use the Firebase Admin SDK or the REST API.
Do writes trigger real-time listeners immediately?
Yes. On the writing client, listeners fire with the new data immediately (local event), before the server confirms the write. Other connected clients receive the update within milliseconds of the server confirming the write.
How do multi-path updates differ from batch writes in Firestore?
Both are atomic. Realtime Database multi-path updates use a single update() call with path keys. Firestore batch writes use a writeBatch builder pattern. Both guarantee all-or-nothing execution.
Talk to an Expert
Our team has built 600+ apps. Get personalized help with your project.
Book a free consultation