Cloud Firestore vs Realtime Database for Offline-First Firebase Apps
Compare Cloud Firestore and Realtime Database for offline-first Firebase apps, with persistence setup and validation steps.
08 Jun 2026, 06:22 UTC

Decision and constraints
You are building a mobile or web application that must keep working when the user loses network access, then synchronize pending changes when connectivity returns. You need to choose between Cloud Firestore and Realtime Database, the two NoSQL databases Firebase supports for client-side sync.
Useful takeaway: for a new offline-first application, start with Cloud Firestore unless your app depends heavily on presence indicators or ultra-low-latency updates to a simple JSON tree. Firestore supports configurable offline persistence, compound queries, and document-level transactions. Realtime Database stores everything in one JSON tree, offers native presence, and bills storage plus downstream bandwidth instead of document operations.
Constraints to settle before choosing:
- Client platforms: Android, iOS, web, or a mix. Persistence defaults differ by platform.
- Query complexity: do you need filtering on multiple fields, subcollections, or collection-group queries?
- Consistency: can the app tolerate brief local-only states, or must related writes succeed or fail together?
- Cost model: are you optimizing for document reads/writes or for stored gigabytes and bytes sent to clients?
- Existing rules: are you extending an app that already uses Realtime Database security rules?
Terms used in this guide
- Offline persistence: the SDK writes to a local cache when the device is offline and sends those writes to Firebase when the network returns.
- Document and collection: Firestore data units. Documents contain fields; collections contain documents and can hold subcollections.
- JSON tree: Realtime Database stores all data as one nested key-value tree, not as separate tables or documents.
- Composite index: a Firestore index on multiple fields, required before certain compound queries can run.
- Downstream bandwidth: data sent from Firebase to connected clients, a Realtime Database billing factor.
Supported options compared
| Factor | Cloud Firestore | Realtime Database |
|---|---|---|
| Data model | Documents in collections, with subcollections | One large JSON tree |
| Queries | Compound filters, ordering, array-contains, collection-group queries | Shallow filtering and sorting on one path |
| Offline persistence | Opt-in on web; enabled by default on Android and iOS SDKs | Enabled by default on Android and iOS; opt-in on web |
| Writes | Document transactions and batched writes | Atomic update at a single path |
| Consistency | Stronger read-after-write semantics within a session | Listener-driven eventual sync |
| Presence | Requires additional logic or Cloud Functions | Native onDisconnect handlers |
| Pricing basis | Reads, writes, deletes, and storage | Storage and downstream bandwidth |
| Best fit | Structured data, complex queries, new mobile/web apps | Presence, chat typing indicators, simple sync workloads |
Trade-offs
Firestore is Firebase's default database for new mobile and web apps. Its document model maps naturally to application objects, and subcollections let you nest related data without duplicating parent documents. Compound queries are powerful, but they often require a composite index created in the Firebase console or with the Firebase CLI before the query returns results. Offline persistence must be turned on explicitly in the web SDK; if you skip it, writes performed while offline can fail or throw errors depending on how you handle the promise.
Realtime Database is a good fit when your data is inherently a single tree and your main requirement is pushing small updates to many connected clients with minimal latency. Its native presence system makes it easier to build online status indicators. The downside is limited querying: you generally filter or sort on one level of the tree, and complex data shapes can force you to denest or duplicate data. Security rules cascade downward, so a broad read rule on a parent path can expose child data unless you write explicit rules for each child path.
Cost is workload-dependent. Firestore charges per document read, write, and delete plus storage. Realtime Database charges for storage and downstream bandwidth. A read-heavy app with small payloads may cost less on Realtime Database; a write-heavy app with structured documents may cost less on Firestore. Use the Firebase pricing calculator with your expected operations per month before committing.
Implementation: enable Firestore offline persistence
This example uses the Firebase JavaScript SDK version 9 or later (modular SDK). Run it in your application's initialization code, typically in a browser build step or a local development server. You need write access to the project repository and a Firebase project with Firestore enabled.
import { initializeApp } from "firebase/app";
import { getFirestore, enableIndexedDbPersistence } from "firebase/firestore";
const firebaseConfig = {
apiKey: "API_KEY",
authDomain: "PROJECT_ID.firebaseapp.com",
projectId: "PROJECT_ID",
};
const app = initializeApp(firebaseConfig);
const db = getFirestore(app);
try {
await enableIndexedDbPersistence(db);
console.log("Firestore offline persistence enabled");
} catch (err) {
console.error("Persistence failed:", err);
}
Placeholders: replace API_KEY and PROJECT_ID with values from your Firebase project settings. The call to enableIndexedDbPersistence must happen before any reads or writes and only once per app instance.
Expected checks:
- Open the app in a supported browser with IndexedDB enabled.
- Confirm the console log shows Firestore offline persistence enabled and no persistence error.
- Turn off network access in browser DevTools, write a document, and confirm the write completes locally without a network error.
- Restore network access and confirm the document appears in the Firebase console and in any other client listening to the same collection.
Risks: persistence can fail in private browsing modes or when IndexedDB is blocked. Catch the rejection and degrade gracefully rather than blocking the app. Do not assume a local write has reached the server until your listener receives a server-sourced update or you reconnect and observe the sync.
Validation: confirm offline writes sync after reconnect
- With persistence enabled, add a listener to a collection using onSnapshot.
- Disable the network interface.
- Create or update a document through the SDK.
- Verify the local listener fires with the new data, proving the write reached the cache.
- Re-enable the network.
- Verify the document appears in the Firebase console and that the listener receives a metadata update indicating the write was acknowledged by the backend.
If the document never appears after reconnect, check the browser console for Firestore errors, confirm the user's security rules permit the write, and verify the client is not still in a disabled network state.
Limitations
- There is no native replication between Firestore and Realtime Database. Moving data between them requires application-level migration, and switching databases means dual-writing during a transition period.
- Firestore composite indexes are not created automatically for every query. The SDK or console usually suggests the missing index when a compound query fails.
- Enabling persistence changes client-side storage. It does not modify server data, but it does write to the browser's IndexedDB or the device's local database.
Rollback
Because enabling persistence alters local client storage, you can undo it by removing the enableIndexedDbPersistence call, redeploying the client, and clearing the site's IndexedDB storage through browser DevTools or by uninstalling and reinstalling the mobile app. No server rollback is required since no server data was changed.
Bottom line: choose Firestore for structured, query-rich offline-first apps; choose Realtime Database for presence-focused, tree-shaped workloads. Validate your pick with the offline-write test above and a pricing estimate based on your expected monthly operations.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.