# Google Cloud Firestore

- Tool: Bubble
- Difficulty: Advanced
- Time required: 2–4 hours
- Last updated: July 2026

## TL;DR

Bubble connects to Google Cloud Firestore through the Firestore REST API v1, using a service account OAuth2 Bearer token stored in a Private header. The two-step auth pattern — exchange service account credentials for a short-lived token, store it in App State, use it on every Firestore call — keeps your Google credentials fully server-side. Ideal for Firebase/Flutter teams building an admin panel over an existing Firestore backend.

## Bubble + Firestore: REST API Integration for Firebase Admin Panels

Teams that build mobile apps with Flutter or FlutterFlow often reach a point where they need an internal admin dashboard — a tool for operations staff to review user submissions, approve records, moderate content, or export reports. Firestore is their database, and Bubble is a natural fit for a no-code admin panel. The challenge is that Bubble doesn't have a native Firebase plugin that works the way FlutterFlow's does.

In FlutterFlow, connecting Firestore is a three-click process: you link your Firebase project, FlutterFlow reads the schema, and you drag data onto a page. In Bubble, Firestore is simply another REST API. The Firestore REST API v1 exposes every collection and document as an HTTPS endpoint, which is exactly what Bubble's API Connector is designed to call.

The authentication story is where most developers get stuck. Firestore does not accept a simple static API key. Instead, it requires a Google OAuth2 Bearer token obtained from a service account. The process has two steps: first, you POST your service account credentials to Google's token endpoint (https://oauth2.googleapis.com/token) and receive a short-lived access token good for one hour. Second, you include that token in an Authorization header on every Firestore API call. In Bubble, you store the token in an App State variable and wire it into a Dynamic header field marked Private.

There is a second wrinkle: Firestore REST responses use typed value wrappers. A string field called 'title' with value 'Hello' does not come back as `{"title": "Hello"}` — it comes back as `{"fields": {"title": {"stringValue": "Hello"}}}`. When Bubble's 'Use as Data' reads this response, it generates deeply nested data types. This guide shows you a practical approach to flatten those nested types into usable Bubble data structures.

A service account with the 'Cloud Datastore User' role bypasses Firebase Security Rules entirely. This is exactly what you want for a Bubble admin panel (you need unrestricted read/write access), but you must scope the IAM role carefully and keep the service account JSON key safe inside the API Connector's Private header — never expose it in client-safe fields or page JavaScript.

Firestore's Spark free plan allows 50,000 reads and 20,000 writes per day. A typical Bubble admin panel operating during business hours will comfortably fit within these limits. If your usage grows, Firestore on the Blaze plan has no default spending cap — set a Google Cloud budget alert before switching.

## Before you start

- A Google Cloud or Firebase project with Cloud Firestore enabled (Firebase Console → Firestore Database → Create database)
- Access to the Google Cloud Console with permissions to create Service Accounts (IAM & Admin → Service Accounts)
- A Bubble app — the free plan is sufficient to build and test API Connector calls; Backend Workflows for server-side scheduled Firestore writes require a paid Bubble plan (Starter+)
- The Bubble API Connector plugin installed in your app (Plugins tab → Add plugins → search 'API Connector' by Bubble → Install)
- Basic understanding of Bubble workflows and App State variables — you will store the OAuth2 token in an App State variable and reference it dynamically in the API Connector header

## Step-by-step guide

### 1. Create a Google service account and download the JSON key

Open the Google Cloud Console (console.cloud.google.com) and select your Firebase project from the project dropdown at the top. Navigate to IAM & Admin → Service Accounts in the left sidebar.

Click 'Create Service Account'. Give it a descriptive name like `bubble-firestore-admin`. Click 'Create and Continue'. On the 'Grant this service account access to project' step, click the role dropdown and search for 'Cloud Datastore User' — select it. This role grants read/write access to Firestore documents without admin-level permissions like creating indexes or deleting databases. Click 'Done'.

Back in the Service Accounts list, click the three-dot menu next to your new service account and select 'Manage keys'. Click 'Add Key' → 'Create new key' → choose JSON format → click 'Create'. A JSON file downloads to your computer automatically. This file contains everything needed for OAuth2 token exchange: `client_email`, `private_key`, `private_key_id`, and `token_uri`.

Open the downloaded JSON file in a text editor. You will need these four values:
- `client_email` (looks like `bubble-firestore-admin@your-project.iam.gserviceaccount.com`)
- `private_key` (a multi-line RSA private key starting with `-----BEGIN RSA PRIVATE KEY-----`)
- `project_id` (your Firebase project ID, e.g. `my-app-12345`)
- `token_uri` (always `https://oauth2.googleapis.com/token`)

Keep this JSON file in a safe, private location. Treat it like a database password — anyone with this file has full Firestore read/write access under the permissions of the 'Cloud Datastore User' role.

```
{
  "type": "service_account",
  "project_id": "your-firebase-project-id",
  "private_key_id": "abc123",
  "private_key": "-----BEGIN RSA PRIVATE KEY-----\n...\n-----END RSA PRIVATE KEY-----\n",
  "client_email": "bubble-firestore-admin@your-firebase-project-id.iam.gserviceaccount.com",
  "token_uri": "https://oauth2.googleapis.com/token"
}
```

**Expected result:** A Google service account named 'bubble-firestore-admin' exists in your project with the 'Cloud Datastore User' IAM role. A JSON key file is downloaded to your computer containing client_email, private_key, and project_id.

### 2. Set up the OAuth2 token exchange call in Bubble API Connector

In your Bubble app, go to the Plugins tab in the left sidebar and click 'Add plugins'. Search for 'API Connector', find the official plugin published by Bubble, and click Install.

Once the plugin appears in your plugins list, click 'API Connector' to open the configuration panel. Click 'Add another API'. Name this group 'Google OAuth2 Token'.

Under Authentication, select 'None' (we are building the auth ourselves).

Click 'Add another call'. Configure this call:
- **Name**: Get Firestore Token
- **Method**: POST
- **URL**: `https://oauth2.googleapis.com/token`
- **Body type**: Form data (not JSON — Google's token endpoint expects `application/x-www-form-urlencoded`)

Add the following body parameters (all as static values, NOT dynamic — these are your service account credentials):
- `grant_type` = `urn:ietf:params:oauth:grant-type:jwt-bearer`
- `assertion` = leave this as dynamic for now — you will need to pass a signed JWT

**Important note on JWT signing**: The Google OAuth2 token endpoint requires a signed JWT assertion, not raw service account credentials. Bubble's API Connector cannot create a signed JWT (that requires cryptographic signing with the RSA private key). The practical solution for Bubble is to use a lightweight proxy — a Cloudflare Worker or a Supabase Edge Function — that accepts your service account credentials in environment variables and returns a valid access token. Alternatively, if your Firestore data is not highly sensitive, consider using the Firebase REST API with a user auth token instead (simpler but scoped to Firebase Authentication users rather than service accounts).

For the most common Bubble + Firestore admin panel use case, deploy a small Cloudflare Worker (free tier, 100,000 requests/day) that holds your service account JSON in an environment variable, signs the JWT internally, and returns a fresh access token. Your Bubble API Connector then calls this Worker endpoint:

```json
{
  "method": "POST",
  "url": "https://your-worker.your-account.workers.dev/firestore-token",
  "headers": {
    "Authorization": "Bearer <your-worker-api-key>",
    "Content-Type": "application/json"
  },
  "body": {}
}
```

Mark the Authorization header **Private**. The Worker returns `{"access_token": "ya29...", "expires_in": 3600}`. Set 'Use as' to **Action** and click **Initialize call** — enter any test body and click Run. Bubble will detect the `access_token` (text) and `expires_in` (number) response fields. Click Save.

Note: RapidDev's team has built dozens of Bubble integrations requiring this kind of JWT-based auth handshake — if the Worker setup feels complex, book a free scoping call at rapidevelopers.com/contact.

```
{
  "method": "POST",
  "url": "https://your-worker.your-account.workers.dev/firestore-token",
  "headers": {
    "Authorization": "Bearer <private>",
    "Content-Type": "application/json"
  },
  "body": {}
}
```

**Expected result:** A 'Get Firestore Token' call is configured in the Bubble API Connector's 'Google OAuth2 Token' group. When initialized, it returns an access_token string and expires_in number that Bubble detects as data fields.

### 3. Configure the Firestore API Connector group with Dynamic Private auth header

Click 'Add another API' in the API Connector panel to create a second group separate from the token exchange. Name this group 'Google Cloud Firestore'.

In the shared headers section, add one header:
- **Header name**: `Authorization`
- **Header value**: `Bearer ` followed by the App State variable reference — in Bubble's dynamic data syntax, this will be your `firestoreToken` App State variable
- Check the **Private** checkbox

Also add a second shared header:
- **Header name**: `Content-Type`
- **Header value**: `application/json`

Set the base URL for this group to:
`https://firestore.googleapis.com/v1/projects/YOUR_PROJECT_ID/databases/(default)/documents`

Replace `YOUR_PROJECT_ID` with your actual Firebase project ID (e.g. `my-app-12345`). This base URL never changes across calls — only the collection path after it changes per call.

Now add your first call in this group — the most essential one:
- **Name**: Get Collection
- **Method**: GET
- **URL**: `/{collection}`
- Add a URL parameter: `collection` (text, dynamic)
- Add a query parameter: `pageSize` (number, value 25)

Set 'Use as' to **Data** (not Action — you want Bubble to treat this as a data source for Repeating Groups). Click **Initialize call**. In the collection field, enter the name of a real collection in your Firestore that has at least one document (e.g. `users` or `orders`). Click Run.

Firestore will return a response like:
```json
{
  "documents": [
    {
      "name": "projects/my-app/databases/(default)/documents/orders/abc123",
      "fields": {
        "status": { "stringValue": "pending" },
        "amount": { "integerValue": "1500" },
        "createdAt": { "timestampValue": "2026-01-15T10:30:00Z" }
      },
      "createTime": "2026-01-15T10:30:00Z",
      "updateTime": "2026-01-15T10:30:00Z"
    }
  ],
  "nextPageToken": "abc..."
}
```

Bubble's 'Use as Data' will detect a list of `documents` items with nested `fields` objects. Each field appears as a sub-object with a single key (`stringValue`, `integerValue`, `timestampValue`, etc.) rather than as a plain value. Click Save — Bubble generates a `Get Collection Document` type with the nested structure detected from this real response.

```
{
  "method": "GET",
  "url": "https://firestore.googleapis.com/v1/projects/YOUR_PROJECT_ID/databases/(default)/documents/{collection}",
  "headers": {
    "Authorization": "Bearer <private>",
    "Content-Type": "application/json"
  },
  "params": {
    "pageSize": "25"
  }
}
```

**Expected result:** The 'Google Cloud Firestore' API Connector group is configured with a Private Dynamic Authorization header. The 'Get Collection' call is initialized against a real Firestore collection and Bubble has detected the documents list with nested field type objects.

### 4. Add create, update, and delete document calls

With the collection GET call working, add the remaining CRUD operations. All three use the same 'Google Cloud Firestore' API Connector group and inherit the shared Private Authorization header.

**Create Document (POST)**
Click 'Add another call':
- Name: Create Document
- Method: POST
- URL: `/{collection}`
- URL parameter: `collection` (dynamic text)
- Body type: JSON
- Body: A JSON object with Firestore's typed field format

Firestore expects you to send values in the same typed wrapper format it returns them in:
```json
{
  "fields": {
    "status": { "stringValue": "{{status}}" },
    "amount": { "integerValue": "{{amount}}" },
    "userId": { "stringValue": "{{userId}}" }
  }
}
```
Make each value a dynamic parameter. Set 'Use as' to **Action**. Initialize with test values matching a real document structure. Bubble detects the created document fields from the response.

**Update Document (PATCH)**
Click 'Add another call':
- Name: Update Document
- Method: PATCH
- URL: `/{docPath}`
- URL parameter: `docPath` (dynamic text) — pass the full document path string (e.g. `orders/abc123`)
- Add query parameter: `updateMask.fieldPaths` = the specific field name you want to update (e.g. `status`). Without this mask, PATCH overwrites the entire document.
- Body: same typed JSON format as Create, containing only the fields you are updating

Set 'Use as' to **Action**.

**Delete Document (DELETE)**
Click 'Add another call':
- Name: Delete Document
- Method: DELETE
- URL: `/{docPath}`
- URL parameter: `docPath` (dynamic text) — the full document path string
- No body required

Set 'Use as' to **Action**. Initialize with a real document path from your test collection.

The full document path (docPath) follows the pattern `{collection}/{documentId}`. Firestore returns the full name as `projects/{projectId}/databases/(default)/documents/{collection}/{documentId}` in GET responses. Strip everything before `/documents/` and store only the collection/docId suffix in a Bubble custom state when loading documents — you will pass this suffix as `docPath` in PATCH and DELETE calls.

```
{
  "create": {
    "method": "POST",
    "url": "https://firestore.googleapis.com/v1/projects/YOUR_PROJECT_ID/databases/(default)/documents/{collection}",
    "body": {
      "fields": {
        "status": { "stringValue": "<dynamic>" },
        "amount": { "integerValue": "<dynamic>" },
        "userId": { "stringValue": "<dynamic>" }
      }
    }
  },
  "update": {
    "method": "PATCH",
    "url": "https://firestore.googleapis.com/v1/projects/YOUR_PROJECT_ID/databases/(default)/documents/{docPath}",
    "params": {
      "updateMask.fieldPaths": "status"
    },
    "body": {
      "fields": {
        "status": { "stringValue": "<dynamic>" }
      }
    }
  },
  "delete": {
    "method": "DELETE",
    "url": "https://firestore.googleapis.com/v1/projects/YOUR_PROJECT_ID/databases/(default)/documents/{docPath}"
  }
}
```

**Expected result:** The 'Google Cloud Firestore' API Connector group has four calls: Get Collection (GET), Create Document (POST), Update Document (PATCH), and Delete Document (DELETE). All are initialized and Bubble has detected their response types.

### 5. Build Bubble workflows: token refresh, collection display, and document writes

Now wire the API Connector calls into Bubble workflows that power your admin panel.

**Page Load Token Workflow**
In the Bubble workflow editor, create a page load workflow triggered by 'Page is loaded'. Add these actions in sequence:
1. Plugins → API Connector → Get Firestore Token (the token exchange call from Step 2)
2. Set App State variable → 'firestoreToken' = Result of Step 1's `access_token`
3. Set App State variable → 'firestoreTokenExpiry' = Current date/time + 55 minutes (set to 55 minutes to give a 5-minute buffer before the actual 1-hour expiry)

After setting the token, add a fourth action:
4. Plugins → API Connector → Get Collection → collection = 'orders' (or your target collection)

Bubble executes this at page load and makes the collection data available for display.

**Repeating Group Setup**
Add a Repeating Group element to your page. Set 'Type of content' to the Bubble data type Bubble created when you initialized the 'Get Collection' call (it will be named something like 'Get Collection Document'). Set 'Data source' to the result of the 'Get Collection' API call from the page load workflow.

Inside the Repeating Group, add text elements. When binding a text field like 'status', the path will be: `Current cell's Get Collection Document's fields's status's stringValue`. The extra navigation through `fields` → `status` → `stringValue` is the Firestore typed wrapper — this is expected. Create a Repeating Group column for each Firestore field your panel needs.

**Document Write Workflow**
For an 'Approve' button that updates a document status field:
1. Trigger: Button is clicked
2. Action: Set App State variable → 'selectedDocPath' = Current cell's Get Collection Document's name (strip the prefix: use Bubble's 'truncate from beginning' text operator to extract only the `{collection}/{docId}` portion after `/documents/`)
3. Action: Plugins → API Connector → Update Document → docPath = App State firestoreDocPath → status = 'approved'
4. Action: Refresh data (re-run the Get Collection call to update the Repeating Group)

**Pagination**
For next-page navigation, store the `nextPageToken` value from the Get Collection response in an App State variable. Add a 'Next Page' button that re-runs the Get Collection call with an additional `pageToken` query parameter set to the stored value.

**Expected result:** Page loads, fetches a fresh access token, stores it in App State, then loads the Firestore collection. A Repeating Group displays all documents with their typed field values correctly bound. The 'Approve' button updates a specific document's status field via PATCH and refreshes the list.

### 6. Handle token expiry and set up Bubble privacy rules

Google service account OAuth2 tokens expire after exactly 1 hour. A long-running Bubble session (a staff member with the admin panel open all day) will see Firestore API calls fail after the first hour. The solution is proactive token refresh.

**Token expiry check**
Create a reusable Bubble workflow called 'Ensure Firestore Token Valid'. In this workflow:
1. Add a Conditional check: 'Only when App State firestoreToken is empty OR Current date/time > App State firestoreTokenExpiry'
2. If the condition is true: run the 'Get Firestore Token' call and update both App State variables as in Step 5's page load workflow

Call 'Ensure Firestore Token Valid' at the beginning of every Bubble workflow that makes a Firestore API call — before the actual Firestore call action. This adds minimal overhead on most workflow runs (the condition fails because the token is still valid) but catches expired tokens automatically.

**Bubble privacy rules**
If your Bubble workflows create or store Firestore data locally (e.g. caching Firestore document IDs or metadata in Bubble's own database for cross-referencing), you must set up Bubble privacy rules for those data types.

Go to Data tab → Data types → [your mirrored type] → Privacy. Add a rule: 'When Current User is logged in and is an admin role (or team member) → Allow find in searches: Yes, Allow reading all fields: Yes'. Without this rule, any authenticated Bubble user can query your cached Firestore data through Bubble's database — even if the Firestore data itself is protected by the service account IAM scope.

**Important Firestore Security Rules note**
The service account with 'Cloud Datastore User' role bypasses Firebase Security Rules entirely — those rules only apply to requests authenticated via Firebase Auth tokens (the ones mobile users get when they sign in with email/password or Google). Your Bubble admin panel using a service account token has full read/write access regardless of what your Security Rules say. This is the correct and expected behavior for an admin panel, but it means the service account JSON key must be treated with the highest level of security. Rotate it annually and store it only in your token proxy's environment variables and in Bubble's Private API Connector header.

**Expected result:** Token refresh logic is wired into all Firestore workflows. Bubble privacy rules protect any locally cached Firestore data. The admin panel remains functional across long sessions without manual token renewal.

## Best practices

- Store the service account JSON key only in your token proxy's environment variables and never paste it directly into Bubble's API Connector body fields or workflow text — those locations are not encrypted and could be exposed in Bubble's logs.
- Use the 'Cloud Datastore User' IAM role, not 'Firebase Admin' or 'Owner'. The narrower role limits access to document read/write operations only, protecting Firestore indexes, security rules, and other Firebase services from accidental modification through the Bubble admin panel.
- Always include 'updateMask.fieldPaths' query parameters on every Firestore PATCH call. Without them, PATCH replaces entire documents and you will lose data. Add one query parameter entry per field being updated.
- Set a Google Cloud budget alert before switching from the Spark free plan to Blaze. Firestore on Blaze has no default spending cap — a Bubble Repeating Group that refreshes every 30 seconds could generate thousands of reads per hour and accumulate unexpected costs.
- Add Bubble privacy rules to any data type that stores Firestore document IDs or metadata locally in Bubble's database. Bubble's database is accessible to all authenticated users by default — privacy rules are the only mechanism that restricts what authenticated users can query.
- Implement proactive token refresh rather than reactive: check the token expiry timestamp stored in App State before each Firestore API call rather than handling 401 errors after they occur. A pre-check adds one conditional step per workflow; recovering from a 401 mid-workflow is more complex to handle cleanly.
- Limit all Firestore GET calls to 25–50 documents using the 'pageSize' query parameter. Without a page size limit, Bubble fetches the entire collection on each page load, which increases Firestore read count, slows the Bubble workflow, and risks hitting the 30-second Bubble workflow timeout on large collections.
- Rotate the service account JSON key annually and set a Google Cloud key expiry. Add a calendar reminder 2 weeks before expiry to update the token proxy environment variable before the key stops working — an expired key breaks the Bubble admin panel for all users simultaneously.

## Use cases

### Firebase/Flutter admin panel in Bubble

Teams with an existing Flutter mobile app backed by Firestore can build an operations dashboard in Bubble that reads the same Firestore collections — reviewing user submissions, approving records, viewing aggregated data — without rebuilding the backend. Bubble writes go directly back to Firestore, so mobile users see the changes immediately.

Prompt example:

```
Build a Bubble admin panel that reads from the Firestore 'orders' collection, displays pending orders in a Repeating Group, and lets staff click 'Approve' to update the order document's status field to 'approved' via a PATCH call to the Firestore REST API.
```

### Cross-platform content moderation tool

If your mobile app stores user-generated content (posts, comments, listings) in Firestore, build a Bubble moderation queue that lists flagged documents from a Firestore 'reports' collection, shows the linked content document, and lets moderators mark items as reviewed or remove the content by deleting the Firestore document.

Prompt example:

```
Create a Bubble workflow that fetches documents from Firestore's 'flagged_content' collection, shows each document's fields in a card layout, and on 'Remove' click sends a DELETE request to the specific document path in the Firestore REST API.
```

### Internal reporting dashboard over Firestore data

Use Bubble's Repeating Groups and charts to display operational metrics stored in Firestore — daily transaction counts, user signups by region, inventory levels — without moving the data to a separate analytics database. Bubble reads Firestore collections on page load and populates visual components with the nested field values.

Prompt example:

```
On page load, run a Bubble API Connector call to GET the Firestore 'daily_metrics' collection with pageSize=30, extract the integerValue fields from each document, and bind them to a bar chart showing the last 30 days of signups.
```

## Troubleshooting

### Initialize call returns 'There was an issue setting up your call' when testing the Firestore Get Collection call

Cause: The access token in App State is empty, expired, or the project ID in the base URL is incorrect. Firestore returns a 401 or 404 that Bubble interprets as a setup failure rather than an API error.

Solution: First, run the 'Get Firestore Token' workflow manually (go to Workflow editor → right-click the token workflow → 'Run this workflow' in test mode) to populate App State with a valid token. Then return to the API Connector and click Initialize again. Also verify the project ID in the Firestore base URL matches your Firebase project ID exactly — find your project ID in the Firebase Console under Project Settings → General.

### Firestore API calls succeed in the morning but start returning 401 Unauthorized errors after a few hours

Cause: The Google OAuth2 access token has expired. Service account tokens are valid for exactly 1 hour. Without token refresh logic, all Firestore calls fail once the token stored in App State becomes stale.

Solution: Implement the 'Ensure Firestore Token Valid' reusable workflow described in Step 6. Add it as the first action in every workflow that makes a Firestore API call. The conditional check (current time > expiry) adds negligible overhead when the token is still valid but automatically refreshes it when needed.

### Repeating Group shows empty or the Bubble data type has deeply nested fields with no usable plain values

Cause: Firestore REST responses use typed value wrappers (stringValue, integerValue, timestampValue). Bubble's 'Use as Data' creates a nested type structure where values live at paths like 'fields → fieldName → stringValue' instead of directly at 'fieldName'.

Solution: This is expected Firestore REST API behavior, not a Bubble bug. In Repeating Group cell bindings, navigate the full path: 'Current cell's Document's fields's status's stringValue'. Alternatively, add a Bubble workflow step after the API call that iterates through the results and populates a separate flat Bubble data type with plain text/number fields extracted from the typed wrappers — more setup but much simpler downstream bindings.

### PATCH (Update Document) call updates one field but removes all other fields from the Firestore document

Cause: The PATCH call is missing the 'updateMask.fieldPaths' query parameter. Without an update mask, Firestore PATCH replaces the entire document with only the fields in the request body.

Solution: In the API Connector's Update Document call, add a query parameter: 'updateMask.fieldPaths' = the name of each field you are updating (one parameter entry per field). For example, if you are only updating 'status', add 'updateMask.fieldPaths = status'. If updating two fields, add two separate 'updateMask.fieldPaths' parameters: one for 'status' and one for 'updatedAt'.

### Firestore returns 403 Forbidden on all calls despite a valid access token

Cause: The service account does not have the 'Cloud Datastore User' IAM role on the project, or it has been granted a role on a different project than the one Firestore is in.

Solution: Go to Google Cloud Console → IAM & Admin → IAM. Find your service account email in the list and verify it has the 'Cloud Datastore User' role listed under the correct project. If the role is missing, click the pencil icon next to the service account, click 'Add another role', search for 'Cloud Datastore User', and save. IAM changes take effect within 60 seconds.

## Frequently asked questions

### Do I need a paid Bubble plan to connect to Firestore?

For basic read and display functionality — fetching Firestore documents and showing them in a Repeating Group — the Bubble free plan is sufficient. API Connector calls and client-side workflows work on the free plan. You need a paid Bubble plan (Starter+) only if you want to use Backend Workflows for server-side Firestore writes triggered by schedules or server-side conditions, rather than by user actions.

### Does the service account bypass Firebase Security Rules?

Yes, entirely. Firebase Security Rules (written in the Firebase Console under Firestore → Rules) apply to requests authenticated with Firebase Auth tokens — the tokens your mobile app users receive when they sign in. A service account OAuth2 token authenticated via Google's IAM system bypasses Security Rules completely. This is intentional for admin tools and is why you must carefully scope the service account to the 'Cloud Datastore User' role and protect the JSON key file.

### Can Bubble receive real-time Firestore updates, like a live dashboard?

Not natively. Firestore's real-time listener feature uses WebSocket connections, which Bubble cannot open from a workflow. For a near-real-time effect, use a Bubble recurring workflow (available on paid plans) that polls the Firestore collection every 30–60 seconds and updates a Bubble database Thing. True real-time push from Firestore to Bubble would require a Backend Workflow endpoint that receives Firestore-triggered webhooks via a Firebase Function — but Firestore itself does not send webhooks natively; you need a Firebase Trigger to forward events.

### What is the correct way to handle the Firestore typed value format in Bubble?

Firestore REST returns all field values as objects with a type key: `{"stringValue": "hello"}`, `{"integerValue": "42"}`, `{"booleanValue": true}`. When Bubble's 'Use as Data' reads a Firestore response, it creates nested data types reflecting this structure. In Repeating Group bindings, navigate the full path: 'Current cell's fields's fieldName's stringValue'. For complex UIs, consider adding a Bubble workflow step after each Firestore fetch that extracts the plain values into a flat custom Bubble data type — the extra step is worth it for cleaner bindings across many elements.

### How do I search or filter Firestore documents from Bubble?

Simple field equality filters can be done via Firestore's structured query endpoint: POST to `https://firestore.googleapis.com/v1/projects/{projectId}/databases/(default)/documents:runQuery` with a `structuredQuery` JSON body. However, this is significantly more complex to configure in Bubble's API Connector than a simple GET. For admin panels with moderate data volumes (under a few thousand documents), the simpler approach is to fetch all documents in a collection with `pageSize` and filter client-side using Bubble's 'Filtered' operator on the list. Use `runQuery` only when collections are too large to fetch all at once.

### Is Firestore better than Supabase for a Bubble admin panel?

If you are choosing a new backend specifically for a Bubble admin panel, Supabase is considerably easier to connect: its PostgREST API requires a simple anon key header, returns plain JSON with no typed value wrappers, and supports SQL-like filtering directly in query parameters. Firestore is the right choice when you already have a Firebase mobile app with existing data in Firestore collections and you want Bubble to manage that same data — switching to a new backend to simplify the Bubble integration would require migrating all existing data.

---

Source: https://www.rapidevelopers.com/bubble-integrations/google-cloud-firestore
© RapidDev — https://www.rapidevelopers.com/bubble-integrations/google-cloud-firestore
