Skip to main content
RapidDev - Software Development Agency
bolt-ai-integrationsBolt Chat + API Route

How to Integrate Bolt.new with Google Cloud Firestore

Use Google Cloud Firestore with Bolt.new by installing the firebase npm package and initializing the Firestore client with your GCP project credentials. The Firebase JS SDK communicates via HTTP and WebSocket — fully compatible with Bolt's WebContainer. Firestore real-time listeners (onSnapshot) work in the preview environment, making live data development smooth. This page focuses on Firestore as a database: data modeling with subcollections, compound queries, and real-time updates.

What you'll learn

  • How to create a Google Cloud / Firebase project and connect Firestore to Bolt.new
  • How to model data with Firestore collections, documents, and subcollections
  • How to use real-time onSnapshot listeners that work in Bolt's WebContainer preview
  • How to perform Firestore CRUD operations and compound queries with composite indexes
  • How Firestore Security Rules protect your data in production
Book a free consultation
4.9Clutch rating
600+Happy partners
17+Countries served
190+Team members
Intermediate21 min read25 minutesDatabaseApril 2026RapidDev Engineering Team
TL;DR

Use Google Cloud Firestore with Bolt.new by installing the firebase npm package and initializing the Firestore client with your GCP project credentials. The Firebase JS SDK communicates via HTTP and WebSocket — fully compatible with Bolt's WebContainer. Firestore real-time listeners (onSnapshot) work in the preview environment, making live data development smooth. This page focuses on Firestore as a database: data modeling with subcollections, compound queries, and real-time updates.

Use Google Cloud Firestore as a Real-Time NoSQL Database in Bolt.new

Google Cloud Firestore is the same technology that powers Firebase's database service — accessed through either the Firebase Console or the Google Cloud Console. Both entry points connect to the same underlying Firestore infrastructure and use the same firebase JavaScript SDK. The difference is primarily in project management: the Firebase Console is streamlined for app developers, while the Google Cloud Console gives enterprise teams finer-grained IAM permissions, Cloud Audit Logs, and the ability to manage Firestore alongside other GCP services like Cloud Run, BigQuery, and Pub/Sub.

For Bolt.new developers, Firestore stands out among database options because its JavaScript SDK is built on HTTP and WebSocket — the only protocols Bolt's WebContainer can use. Unlike PostgreSQL or MongoDB which require TCP sockets (blocked in WebContainers), Firestore works natively in the preview environment. Real-time onSnapshot listeners push changes to your UI instantly, and you can watch two browser tabs sync live during development without deploying first. This makes Firestore one of the smoothest database integrations possible in Bolt.

This page goes deeper than the general Firebase tutorial: it focuses on Firestore's data modeling capabilities — structuring data with subcollections, designing for query patterns, handling compound queries that require composite indexes, and writing Security Rules that protect your data without breaking your app. If you need a database that excels at hierarchically organized data, real-time collaboration, and offline-first mobile-and-web apps, Firestore is a powerful choice with a direct path to production from Bolt.

Integration method

Bolt Chat + API Route

Firestore's JavaScript SDK (via the firebase npm package) communicates over HTTP and WebSocket protocols, matching Bolt's WebContainer runtime exactly — no TCP sockets required. You initialize Firestore with your Firebase/GCP project config, then use the SDK directly from client-side React components or server-side API routes. Real-time onSnapshot listeners push document changes to your UI instantly and work in Bolt's WebContainer preview, letting you test live updates before deploying.

Prerequisites

  • A Google account to access the Firebase Console at console.firebase.google.com or the Google Cloud Console at console.cloud.google.com
  • A Firebase or GCP project with Firestore Database enabled in Native mode (not Datastore mode)
  • Your Firebase web app configuration object — found in Firebase Console → Project Settings → Your apps → Web app config
  • A Bolt.new project using Vite or Next.js (both work with the Firebase JS SDK)
  • Basic understanding of NoSQL document/collection data structures

Step-by-step guide

1

Create a Firebase Project and Enable Firestore

Navigate to console.firebase.google.com and click 'Add project'. Give your project a name — this creates a Google Cloud project in the background. You can optionally enable Google Analytics; it does not affect Firestore functionality. Once the project is created, click the web icon (</>) on the Project Overview to register a web app. Give the app a nickname, skip Firebase Hosting for now, and click 'Register app'. Firebase displays your configuration object — a plain JavaScript object with six fields: apiKey, authDomain, projectId, storageBucket, messagingSenderId, and appId. Copy this entire object and keep it handy. Next, enable Firestore. In the left sidebar, click 'Firestore Database' → 'Create database'. Choose a region close to your users (this cannot be changed later). For the security rules mode, select 'Start in test mode' — this allows all reads and writes for 30 days, which is sufficient for development. Click 'Enable'. Firestore will provision in about 30 seconds. If you prefer to access your project through the Google Cloud Console (console.cloud.google.com), the same Firestore database is visible under Firestore in the left menu. GCP also gives you access to Cloud Audit Logs, IAM roles for service accounts, and Firestore's export/import pipeline to Google Cloud Storage — useful for backups and data migrations. For Bolt development, the Firebase Console is simpler; switch to the GCP Console only when you need enterprise features. One key note: Firebase config values (apiKey, projectId, etc.) are designed to be public. They identify your project and are safe to commit to source code or include in client-side bundles. Security is enforced by Firestore Security Rules, not by keeping the config private. This is different from API keys for paid services — your Firebase apiKey being visible in browser DevTools is expected and not a vulnerability.

Bolt.new Prompt

Set up Firebase Firestore in my Bolt.new project. Install the firebase npm package. Create a src/lib/firebase.ts file that initializes Firebase using environment variables (VITE_FIREBASE_API_KEY, VITE_FIREBASE_AUTH_DOMAIN, VITE_FIREBASE_PROJECT_ID, VITE_FIREBASE_STORAGE_BUCKET, VITE_FIREBASE_MESSAGING_SENDER_ID, VITE_FIREBASE_APP_ID). Use a getApps().length check to prevent re-initialization during hot reload. Export the initialized Firestore db instance.

Paste this in Bolt.new chat

src/lib/firebase.ts
1// src/lib/firebase.ts
2import { initializeApp, getApps, getApp } from 'firebase/app';
3import { getFirestore } from 'firebase/firestore';
4
5const firebaseConfig = {
6 apiKey: import.meta.env.VITE_FIREBASE_API_KEY,
7 authDomain: import.meta.env.VITE_FIREBASE_AUTH_DOMAIN,
8 projectId: import.meta.env.VITE_FIREBASE_PROJECT_ID,
9 storageBucket: import.meta.env.VITE_FIREBASE_STORAGE_BUCKET,
10 messagingSenderId: import.meta.env.VITE_FIREBASE_MESSAGING_SENDER_ID,
11 appId: import.meta.env.VITE_FIREBASE_APP_ID,
12};
13
14// Prevent re-initialization on Vite hot module replacement
15const app = getApps().length === 0 ? initializeApp(firebaseConfig) : getApp();
16
17export const db = getFirestore(app);
18export default app;

Pro tip: Create a .env file in your Bolt project root and paste your Firebase config values with the VITE_ prefix. Example: VITE_FIREBASE_PROJECT_ID=my-project-id. For Next.js projects, use NEXT_PUBLIC_ prefix instead.

Expected result: Firebase initializes without errors. The db export is available for Firestore operations throughout the app.

2

Model Your Data with Collections, Documents, and Subcollections

Firestore organizes data in a hierarchy: your database contains collections, each collection contains documents, and each document can contain subcollections. Think of it as: Database → Folders (collections) → Files (documents) → Nested folders (subcollections). A document is a map of key-value fields — strings, numbers, booleans, timestamps, arrays, nested maps, and references. There are no JOINs and no foreign keys; related data is either embedded in a document, stored in a subcollection, or linked by storing a document ID as a field. Subcollections are the key Firestore data modeling tool. A 'posts' collection can have a 'comments' subcollection under each post document. Reading a post does not automatically read its comments — you query the subcollection separately. This is fundamentally different from embedding comments as an array inside the post document: embedded arrays load entirely on each read and cannot be queried independently, but they cost fewer reads. Use subcollections when you expect many child records or need to query them independently. Use embedded arrays for small, fixed lists that you always need alongside the parent. Compound queries — filtering on multiple fields, or combining a filter with ordering — require composite indexes in Firestore. You cannot, for example, query where status == 'active' AND orderBy createdAt without a composite index on those two fields. Firestore will return a console error with a direct link to create the index in the Firebase Console with one click. Index creation takes 1-3 minutes. Plan your query patterns early and create indexes proactively. You can view and manage all indexes under Firestore Database → Indexes in the Firebase Console. Document ID strategy matters for performance. Auto-generated IDs (addDoc) are random strings that distribute writes evenly across Firestore's storage shards — use these by default. Sequential IDs (like timestamps or incrementing integers) can cause hotspot issues at scale because they route writes to the same shard. For lookups by a specific value (e.g., finding a user by their email), store that value as a field and query with where() rather than encoding it in the document ID.

Bolt.new Prompt

Show me the Firestore data model for a project management app with projects, tasks, and comments. Create TypeScript interfaces in src/types/firestore.ts for Project, Task, and Comment documents. Projects are a top-level collection. Tasks are a subcollection under each project. Comments are a subcollection under each task. Each type should have appropriate fields including id, createdAt, and updatedAt.

Paste this in Bolt.new chat

src/types/firestore.ts
1// src/types/firestore.ts
2import { Timestamp } from 'firebase/firestore';
3
4export interface Project {
5 id: string;
6 name: string;
7 description: string;
8 ownerId: string;
9 memberIds: string[];
10 status: 'active' | 'archived';
11 createdAt: Timestamp;
12 updatedAt: Timestamp;
13}
14
15export interface Task {
16 id: string;
17 title: string;
18 description: string;
19 assigneeId: string | null;
20 status: 'todo' | 'in-progress' | 'review' | 'done';
21 priority: 'low' | 'medium' | 'high';
22 dueDate: Timestamp | null;
23 createdAt: Timestamp;
24 updatedAt: Timestamp;
25}
26
27export interface Comment {
28 id: string;
29 body: string;
30 authorId: string;
31 authorName: string;
32 createdAt: Timestamp;
33}
34
35// Firestore path helpers
36export const PATHS = {
37 projects: () => 'projects',
38 project: (projectId: string) => `projects/${projectId}`,
39 tasks: (projectId: string) => `projects/${projectId}/tasks`,
40 task: (projectId: string, taskId: string) => `projects/${projectId}/tasks/${taskId}`,
41 comments: (projectId: string, taskId: string) =>
42 `projects/${projectId}/tasks/${taskId}/comments`,
43};

Pro tip: Firestore charges per document read, write, and delete — not per query. A query that returns 50 documents costs 50 reads regardless of how many documents exist in the collection. Design subcollection boundaries to minimize unnecessary reads.

Expected result: TypeScript types are defined for all Firestore documents. Path helpers make it easy to construct collection and document references without typos.

3

Implement Real-Time CRUD with onSnapshot Listeners

Firestore's onSnapshot is the standout feature for Bolt development. Unlike getDoc or getDocs (one-time fetches), onSnapshot registers a persistent listener that fires immediately with the current data, then fires again every time the data changes — in any browser tab connected to the same Firestore database. This works inside Bolt's WebContainer preview environment because Firestore's SDK uses WebSocket under the hood, which WebContainers support natively. You can open two browser windows in Bolt's preview side by side and watch data synchronize in real time. Every onSnapshot call returns an unsubscribe function. You must call this function when the component unmounts — typically in a React useEffect cleanup return. Failing to unsubscribe causes memory leaks and, more critically, Firestore connection errors after hot module replacements where the same snapshot is registered twice. The getApps() guard in your firebase.ts initialization handles the app-level duplicate, but you must handle the listener-level duplicate through proper cleanup. For write operations, Firestore provides addDoc (creates a document with an auto-generated ID), setDoc (creates or overwrites a document with a specific ID), updateDoc (merges fields into an existing document without overwriting the whole document), and deleteDoc. Use serverTimestamp() for createdAt and updatedAt fields — it uses Firebase's server clock rather than the client clock, preventing issues with user devices that have incorrect system times. Batch writes (writeBatch) let you execute multiple operations atomically — all succeed or all fail — useful for maintaining consistency across multiple documents, like updating a task status while also updating the project's lastActivityAt field. For queries, the query() function takes a collection reference and one or more constraint functions: where(), orderBy(), limit(), startAfter(), and endBefore(). Combining where() on one field with orderBy() on a different field requires a composite index. The Firebase Console will display a clickable link in the error message that creates the index automatically — this is the standard Firestore development workflow. For production, create indexes proactively based on your known query patterns.

Bolt.new Prompt

Create a React hook useProjectTasks(projectId) in src/hooks/useProjectTasks.ts that subscribes to the tasks subcollection of a given project using onSnapshot. The hook should return tasks sorted by createdAt descending, a loading state, and an error state. Include unsubscribe cleanup. Also export CRUD functions: createTask(projectId, task), updateTask(projectId, taskId, changes), and deleteTask(projectId, taskId). Use the db from src/lib/firebase.ts.

Paste this in Bolt.new chat

src/hooks/useProjectTasks.ts
1// src/hooks/useProjectTasks.ts
2import { useState, useEffect } from 'react';
3import {
4 collection,
5 query,
6 orderBy,
7 onSnapshot,
8 addDoc,
9 updateDoc,
10 deleteDoc,
11 doc,
12 serverTimestamp,
13} from 'firebase/firestore';
14import { db } from '@/lib/firebase';
15import type { Task } from '@/types/firestore';
16
17export function useProjectTasks(projectId: string) {
18 const [tasks, setTasks] = useState<Task[]>([]);
19 const [loading, setLoading] = useState(true);
20 const [error, setError] = useState<Error | null>(null);
21
22 useEffect(() => {
23 if (!projectId) return;
24
25 const tasksRef = collection(db, 'projects', projectId, 'tasks');
26 const q = query(tasksRef, orderBy('createdAt', 'desc'));
27
28 const unsubscribe = onSnapshot(
29 q,
30 (snapshot) => {
31 const taskList = snapshot.docs.map((d) => ({
32 id: d.id,
33 ...d.data(),
34 })) as Task[];
35 setTasks(taskList);
36 setLoading(false);
37 },
38 (err) => {
39 setError(err);
40 setLoading(false);
41 }
42 );
43
44 // Must unsubscribe to prevent memory leaks and duplicate listeners on HMR
45 return () => unsubscribe();
46 }, [projectId]);
47
48 return { tasks, loading, error };
49}
50
51export async function createTask(
52 projectId: string,
53 task: Omit<Task, 'id' | 'createdAt' | 'updatedAt'>
54) {
55 const tasksRef = collection(db, 'projects', projectId, 'tasks');
56 return addDoc(tasksRef, {
57 ...task,
58 createdAt: serverTimestamp(),
59 updatedAt: serverTimestamp(),
60 });
61}
62
63export async function updateTask(
64 projectId: string,
65 taskId: string,
66 changes: Partial<Omit<Task, 'id' | 'createdAt'>>
67) {
68 const taskRef = doc(db, 'projects', projectId, 'tasks', taskId);
69 return updateDoc(taskRef, { ...changes, updatedAt: serverTimestamp() });
70}
71
72export async function deleteTask(projectId: string, taskId: string) {
73 const taskRef = doc(db, 'projects', projectId, 'tasks', taskId);
74 return deleteDoc(taskRef);
75}

Pro tip: Firestore's onSnapshot works in Bolt's WebContainer preview — open two browser tabs pointing to your Bolt preview URL and add a task in one tab. It appears instantly in the other tab without any reload. This makes Firestore one of the best databases for testing real-time features during development.

Expected result: The useProjectTasks hook subscribes to live task updates. Creating, updating, or deleting a task in any browser tab is reflected instantly across all connected clients — including inside Bolt's preview environment.

4

Write Firestore Security Rules to Protect Your Data

Firestore Security Rules are the access control layer for your database. They run server-side on Firebase's infrastructure and cannot be bypassed by client-side code, even if someone modifies your JavaScript bundle. Rules evaluate each read, write, create, update, and delete operation and return allow or deny. When you created your Firestore database in test mode, Firebase set a permissive rule that allows all access for 30 days — you must replace this before production. Rules use a declarative syntax with match statements that target document paths. The request object contains the incoming operation details: request.auth (the authenticated user's UID and token claims), request.resource.data (the data being written), and request.method (read/write). The resource object represents the current document being read or written. You can write helper functions within the rules file to avoid repeating logic — for example, an isOwner() function that checks if request.auth.uid matches a document's ownerId field. For the project management data model from Step 2, a practical rule set allows any authenticated user to read projects where their UID is in the memberIds array, but only the project owner to update or delete the project. Tasks within a project inherit this access pattern — any project member can create and update tasks, but only the task's assignee or the project owner can delete them. Comments can be edited only by their author. Rules do not cascade: a rule that allows reading a project document does not automatically allow reading its tasks subcollection. You must write explicit match statements for each path level. The Firebase Console's Rules Playground lets you simulate operations and verify that your rules behave correctly before publishing. Test with both authenticated and unauthenticated requests, and test edge cases like a user trying to access another user's data. Publish rules from Firebase Console → Firestore Database → Rules → Publish. Changes take effect globally within about 60 seconds.

Bolt.new Prompt

Generate Firestore Security Rules for my project management app. Users must be authenticated to access any data. A user can read a project only if their UID is in the project's memberIds array. Only the project owner (ownerId field) can update or delete the project document. Any project member can create and update tasks in the project's tasks subcollection. Only the task creator (stored in a createdBy field) or the project owner can delete a task. Any project member can read and create comments on tasks.

Paste this in Bolt.new chat

firestore.rules
1// firestore.rules
2rules_version = '2';
3service cloud.firestore {
4 match /databases/{database}/documents {
5
6 // Helper functions
7 function isAuthenticated() {
8 return request.auth != null;
9 }
10
11 function isProjectMember(projectId) {
12 return isAuthenticated() &&
13 request.auth.uid in get(/databases/$(database)/documents/projects/$(projectId)).data.memberIds;
14 }
15
16 function isProjectOwner(projectId) {
17 return isAuthenticated() &&
18 request.auth.uid == get(/databases/$(database)/documents/projects/$(projectId)).data.ownerId;
19 }
20
21 // Projects collection
22 match /projects/{projectId} {
23 allow read: if isProjectMember(projectId) || isProjectOwner(projectId);
24 allow create: if isAuthenticated();
25 allow update: if isProjectOwner(projectId);
26 allow delete: if isProjectOwner(projectId);
27
28 // Tasks subcollection
29 match /tasks/{taskId} {
30 allow read: if isProjectMember(projectId) || isProjectOwner(projectId);
31 allow create: if isProjectMember(projectId) || isProjectOwner(projectId);
32 allow update: if isProjectMember(projectId) || isProjectOwner(projectId);
33 allow delete: if isProjectOwner(projectId) ||
34 (isAuthenticated() && request.auth.uid == resource.data.createdBy);
35
36 // Comments subcollection
37 match /comments/{commentId} {
38 allow read: if isProjectMember(projectId) || isProjectOwner(projectId);
39 allow create: if isProjectMember(projectId) || isProjectOwner(projectId);
40 allow update, delete: if isAuthenticated() &&
41 request.auth.uid == resource.data.authorId;
42 }
43 }
44 }
45 }
46}

Pro tip: The get() function in rules performs a document read — it counts against your Firestore read quota. For high-traffic apps, denormalize membership data (store it directly on the document being accessed) to avoid expensive get() calls in rules.

Expected result: Firestore rules are published. Authenticated project members can read and write tasks; non-members cannot access the data. Verify using the Rules Playground in the Firebase Console before deploying to production.

5

Deploy to Netlify and Configure Production Environment Variables

During development in Bolt's WebContainer, your Firestore reads, writes, and real-time listeners work in the preview. However, two important limitations apply before you deploy. First, incoming webhooks from external services cannot reach the WebContainer — there is no publicly accessible URL for the preview environment. If you plan to trigger Firestore writes from an external service (for example, a payment webhook that records a transaction), you must deploy first and then register the deployed URL as the webhook endpoint. Second, if your app uses Firebase Authentication with OAuth providers like Google, the OAuth redirect URI must point to your deployed domain — the preview URL is dynamic and cannot be pre-registered. To deploy, click the Publish button in Bolt's top-right corner. If you have Netlify connected via Settings → Applications, Bolt deploys automatically to a *.netlify.app URL in about 30 seconds. After deploying, go to Netlify Dashboard → Site Configuration → Environment Variables and add all six Firebase config values: VITE_FIREBASE_API_KEY, VITE_FIREBASE_AUTH_DOMAIN, VITE_FIREBASE_PROJECT_ID, VITE_FIREBASE_STORAGE_BUCKET, VITE_FIREBASE_MESSAGING_SENDER_ID, and VITE_FIREBASE_APP_ID. Trigger a redeploy after adding environment variables — Netlify injects them at build time, so the currently deployed build does not have them yet. For Firebase Authentication with Google or GitHub login, add your deployed Netlify domain to the Firebase Console. Go to Firebase Console → Authentication → Settings → Authorized domains and add your *.netlify.app URL (and your custom domain if you have one). Without this step, OAuth login returns an auth/unauthorized-domain error on the live site. Test the full authentication flow on the deployed URL after adding the authorized domain. If your app scales beyond Netlify's free tier, exporting the Bolt project to GitHub and connecting it to Vercel works identically — Firestore and Firebase Auth are cloud services that don't care which hosting provider serves your frontend.

Bolt.new Prompt

My Bolt.new app uses Firebase Firestore and is ready to deploy to Netlify. Add a deployment checklist comment to the top of src/lib/firebase.ts listing: 1) environment variables to set in Netlify, 2) authorized domains to add in Firebase Console for Auth, 3) Firestore security rules to publish before going live.

Paste this in Bolt.new chat

src/lib/firebase.ts
1// src/lib/firebase.ts
2/**
3 * DEPLOYMENT CHECKLIST
4 * Before going live, complete these steps:
5 *
6 * 1. NETLIFY ENVIRONMENT VARIABLES (Site Configuration Environment Variables)
7 * - VITE_FIREBASE_API_KEY
8 * - VITE_FIREBASE_AUTH_DOMAIN
9 * - VITE_FIREBASE_PROJECT_ID
10 * - VITE_FIREBASE_STORAGE_BUCKET
11 * - VITE_FIREBASE_MESSAGING_SENDER_ID
12 * - VITE_FIREBASE_APP_ID
13 * Then: Trigger a redeploy to inject env vars into the build.
14 *
15 * 2. FIREBASE AUTHORIZED DOMAINS (Firebase Console Authentication Settings Authorized domains)
16 * Add your deployed URL, e.g.:
17 * - your-app.netlify.app
18 * - your-custom-domain.com
19 * Required for Google/GitHub OAuth login to work on production.
20 *
21 * 3. FIRESTORE SECURITY RULES (Firebase Console Firestore Database Rules)
22 * Replace test mode rules with production rules that restrict access
23 * to authenticated users only. Test mode expires after 30 days.
24 */
25
26import { initializeApp, getApps, getApp } from 'firebase/app';
27import { getFirestore } from 'firebase/firestore';
28import { getAuth } from 'firebase/auth';
29
30const firebaseConfig = {
31 apiKey: import.meta.env.VITE_FIREBASE_API_KEY,
32 authDomain: import.meta.env.VITE_FIREBASE_AUTH_DOMAIN,
33 projectId: import.meta.env.VITE_FIREBASE_PROJECT_ID,
34 storageBucket: import.meta.env.VITE_FIREBASE_STORAGE_BUCKET,
35 messagingSenderId: import.meta.env.VITE_FIREBASE_MESSAGING_SENDER_ID,
36 appId: import.meta.env.VITE_FIREBASE_APP_ID,
37};
38
39const app = getApps().length === 0 ? initializeApp(firebaseConfig) : getApp();
40
41export const db = getFirestore(app);
42export const auth = getAuth(app);
43export default app;

Pro tip: Firestore real-time listeners work in Bolt's WebContainer preview during development, but test Firebase Authentication OAuth flows on your deployed Netlify URL — OAuth redirect URIs must point to a stable public domain, not Bolt's dynamic preview URL.

Expected result: App is deployed to Netlify with all Firebase environment variables set. Google/GitHub OAuth login works on the live site after adding the Netlify domain to Firebase's authorized domains. Firestore production security rules are published.

Common use cases

Real-Time Dashboard with Live Metrics

Build a business dashboard where key metrics — orders, signups, revenue — update in real time as data flows into Firestore. onSnapshot listeners keep every panel current without manual refresh. Since listeners work in Bolt's preview, you can develop and demo the live-updating UI before deploying.

Bolt.new Prompt

Build a real-time business dashboard using Firebase Firestore. Create a 'metrics' collection where each document represents a daily snapshot with fields: date, totalOrders, totalRevenue, newSignups, and activeUsers. Use Firestore's onSnapshot to listen for changes and update the dashboard cards in real time. Show a line chart of totalRevenue over the last 7 days using Recharts. Query documents ordered by date descending, limited to 7.

Copy this prompt to try it in Bolt.new

Nested Comments System with Subcollections

Implement a post-and-comments feature using Firestore subcollections — a natural fit for hierarchical data like posts containing comments, or projects containing tasks. Subcollections let you read only the comments for a specific post without loading all comments across the database.

Bolt.new Prompt

Build a blog post and comments system using Firebase Firestore subcollections. The top-level 'posts' collection has documents with fields: title, body, authorId, authorName, and createdAt. Each post document has a 'comments' subcollection with fields: body, authorId, authorName, and createdAt. Build a PostPage that displays the post and all its comments, with a form to add new comments. Use onSnapshot on the subcollection for real-time comment updates.

Copy this prompt to try it in Bolt.new

Multi-User Chat with Presence Tracking

Create a real-time chat app where messages stream in via onSnapshot and user presence (online/offline) is tracked in a Firestore document. The Firebase JS SDK's WebSocket connection makes this snappy in Bolt's preview, and you can test with multiple browser tabs before deploying.

Bolt.new Prompt

Build a real-time chat room using Firebase Firestore. Create a 'rooms' collection where each room document has a 'messages' subcollection with fields: text, senderId, senderName, and timestamp. Use onSnapshot on the messages subcollection to display new messages as they arrive. Add a 'presence' document in each room that tracks which users are currently online (update on mount, delete on unmount using cleanup). Limit the initial message load to the last 50 messages ordered by timestamp.

Copy this prompt to try it in Bolt.new

Troubleshooting

Firestore query fails with 'The query requires an index' and a URL in the error

Cause: Compound queries combining a where() filter on one field with an orderBy() on a different field require a composite index in Firestore. The index does not exist yet.

Solution: Click the URL in the error message — it opens the Firebase Console with the required composite index pre-configured. Click 'Create index'. Index creation takes 1-3 minutes. After the index status changes from 'Building' to 'Enabled', retry the query.

onSnapshot listener fires repeatedly or causes 'FirebaseError: Missing or insufficient permissions' after a hot module reload in Bolt

Cause: During Vite hot module replacement, the component remounts and registers a new onSnapshot listener before the previous one is cleaned up. If the Firebase app is also re-initialized, it can trigger permission evaluation issues.

Solution: Ensure your useEffect returns the unsubscribe function from onSnapshot. Also verify lib/firebase.ts uses the getApps().length check to prevent re-initializing the Firebase app on each HMR cycle.

typescript
1useEffect(() => {
2 const unsubscribe = onSnapshot(q, (snapshot) => {
3 // handle snapshot
4 });
5 // This cleanup runs before the next render cycle and on unmount
6 return () => unsubscribe();
7}, []);

Google Sign-In returns 'auth/unauthorized-domain' error on the deployed site

Cause: The deployed domain is not listed in Firebase Console's authorized domains. Firebase blocks OAuth redirects from unregistered origins.

Solution: In Firebase Console, go to Authentication → Settings → Authorized domains and add your Netlify URL (e.g., your-app.netlify.app) and any custom domain. For local development or Bolt's preview, use signInWithPopup instead of signInWithRedirect — popups bypass the domain check.

typescript
1// Use popup for dev/preview environments
2import { signInWithPopup, GoogleAuthProvider } from 'firebase/auth';
3await signInWithPopup(auth, new GoogleAuthProvider());
4
5// Use redirect for production (after registering domain)
6import { signInWithRedirect, GoogleAuthProvider } from 'firebase/auth';
7await signInWithRedirect(auth, new GoogleAuthProvider());

Firestore reads return empty results even though documents exist in the Firebase Console

Cause: Firestore Security Rules are blocking the read. This commonly happens when test mode's 30-day access window has expired, or when rules were tightened without testing all read paths.

Solution: Open Firebase Console → Firestore Database → Rules and check the current rules. Use the Rules Playground to simulate your specific read operation. If you are in development, temporarily allow all authenticated reads. In production, verify that your rules correctly grant access for the user making the request.

typescript
1// Development-only: allow all reads/writes for authenticated users
2rules_version = '2';
3service cloud.firestore {
4 match /databases/{database}/documents {
5 match /{document=**} {
6 allow read, write: if request.auth != null;
7 }
8 }
9}

Best practices

  • Use onSnapshot for data your UI displays in real time, and getDocs for one-time reads like exports or admin operations — listeners hold open WebSocket connections and are not needed for data that only needs to load once
  • Always return the unsubscribe function from onSnapshot inside a useEffect cleanup to prevent memory leaks and duplicate listener registration during Vite hot module replacement
  • Use serverTimestamp() for createdAt and updatedAt fields instead of new Date() or Date.now() — it uses Firebase's server clock and prevents issues with client devices that have incorrect system time
  • Design your Firestore data model around your query patterns before writing code — Firestore cannot perform full-text search, JOINs, or ad-hoc aggregations across documents, so data that needs to be queried together should live in the same collection or be denormalized
  • Create composite indexes proactively in the Firebase Console — any compound query (filtering on multiple fields, or filtering + ordering on different fields) will fail at runtime without one
  • Tighten Firestore Security Rules before deploying to production — test mode's open access expires after 30 days, and leaving rules open exposes your entire database to public reads and writes
  • Firestore's onSnapshot works in Bolt's WebContainer preview — use this during development to verify real-time sync behavior before deploying, since two browser tabs in the preview will sync live against the same Firestore database
  • Test Firebase Authentication OAuth flows on your deployed Netlify URL, not in Bolt's preview — OAuth redirect URIs must be pre-registered and the preview URL is dynamic and changes between sessions

Alternatives

Frequently asked questions

Does Firestore work in Bolt's WebContainer preview?

Yes — Firestore's JavaScript SDK communicates over HTTP and WebSocket, both of which are fully supported in Bolt's WebContainer runtime. Firestore CRUD operations and real-time onSnapshot listeners all work in the preview. You can open two browser tabs pointing to your Bolt preview URL and watch Firestore data sync between them in real time during development, without deploying first.

What is the difference between this page and the Firebase integration page?

The Firebase page covers the full Firebase suite — Firestore, Auth, Storage, and Cloud Messaging — with a focus on getting started. This Google Cloud Firestore page goes deeper into Firestore specifically: data modeling with subcollections, compound queries and composite indexes, Firestore Security Rules, and how to structure your collections for production. Use the Firebase page for a broad overview and this page when Firestore is your primary backend.

Can I use Google Cloud Firestore without the Firebase Console?

Yes. The Firebase Console and the Google Cloud Console both manage the same Firestore database. You can create a project in the Google Cloud Console, enable the Cloud Firestore API, create a Firebase web app for the client configuration, and manage everything from GCP. The JavaScript SDK is the same regardless of which console you use. The GCP Console adds IAM role management, Cloud Audit Logs, and native integration with other Google Cloud services.

How do I handle Firestore in a Next.js Bolt.new project versus a Vite project?

The Firebase SDK works in both. In a Vite project, use import.meta.env.VITE_* for environment variables. In a Next.js project, use process.env.NEXT_PUBLIC_* for client-side variables (safe to expose) and process.env.* (no prefix) for server-side API routes. The Firestore initialization and hook patterns are identical between frameworks — only the environment variable prefix and file location (src/lib vs lib) differ.

Do incoming webhooks work with Firestore in Bolt's WebContainer?

Firestore real-time listeners (outbound WebSocket connections from your app to Firestore) work perfectly in the WebContainer preview. However, incoming webhooks from external services cannot reach the WebContainer — there is no publicly accessible URL for Bolt's preview. If you need an external service to write to Firestore via a webhook, deploy to Netlify first and register your deployed URL as the webhook endpoint.

How do I migrate my Firestore data if I need to restructure my collections?

Firestore has no built-in migration tools comparable to SQL ALTER TABLE. The standard approach is to write a migration script using the Firebase Admin SDK that reads the old structure and writes the new structure. For small datasets (under 10,000 documents), run this script locally using Node.js with your service account credentials. For larger migrations, use Google Cloud Dataflow with the Firestore connector or write Cloud Functions triggered by a Pub/Sub message to process documents in batches.

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.