Skip to main content
RapidDev - Software Development Agency
retool-integrationsRetool Native Resource

How to Integrate Retool with Google Cloud Firestore

Connect Retool to Google Cloud Firestore by adding a Firebase Resource in the Resources tab, authenticating with a Google service account JSON key, and selecting 'Firestore' as the service type per query. You can then build document CRUD interfaces, run compound queries with filters and ordering, paginate with startAfter cursors, and display nested Firestore data in Retool Tables — all without writing API client code.

What you'll learn

  • How to configure a Retool Firebase Resource for Firestore access using a Google service account
  • How to execute Firestore get, query, add, set, update, and delete operations from Retool queries
  • How to build compound Firestore queries with multiple filters, ordering, and cursor-based pagination
  • How to display nested Firestore document fields and subcollections in Retool Tables
  • How to build a complete Firestore CRUD admin panel with create, edit, and delete workflows
Book a free consultation
4.9Clutch rating
600+Happy partners
17+Countries served
190+Team members
Intermediate17 min read20 minutesDatabaseLast updated April 2026RapidDev Engineering Team
TL;DR

Connect Retool to Google Cloud Firestore by adding a Firebase Resource in the Resources tab, authenticating with a Google service account JSON key, and selecting 'Firestore' as the service type per query. You can then build document CRUD interfaces, run compound queries with filters and ordering, paginate with startAfter cursors, and display nested Firestore data in Retool Tables — all without writing API client code.

Quick facts about this guide
FactValue
ToolGoogle Cloud Firestore
CategoryDatabase
MethodRetool Native Resource
DifficultyIntermediate
Time required20 minutes
Last updatedApril 2026

Build a Firestore Document Management Panel with Retool

Google Cloud Firestore is widely used as the backend database for mobile apps, web applications, and serverless platforms. However, Firestore's own data console in the Google Cloud Console is a development tool — it is not designed for operations teams who need to search, filter, and edit production data daily. Retool provides a purpose-built interface for Firestore operations by exposing the Firestore API through its native Firebase connector and wrapping it in drag-and-drop UI components.

With a Retool–Firestore integration, ops teams can query Firestore collections with compound filters (combining multiple where clauses and ordering), paginate through large collections using Firestore's cursor-based startAfter mechanism, and perform document CRUD operations through polished forms without needing access to the Google Cloud Console. Teams already using Firestore for their app data can use Retool to build admin panels for customer support, data correction, and operational reporting.

Firestore's document/collection model differs fundamentally from relational databases — there are no JOINs, no schema enforcement, and pagination works via document cursors rather than OFFSET. This page covers these Firestore-specific patterns and how to work with them effectively in Retool's query editor and component system.

Integration method

Retool Native Resource

Retool connects to Firestore through its native Firebase Resource, which authenticates with a Google service account JSON key and selects 'Firestore' as the service type per query. The query editor provides action pickers for get, query, add, set, update, and delete operations on documents and collections — no REST endpoint knowledge required. All requests are proxied server-side through Retool, keeping service account credentials off the browser.

Prerequisites

  • A Google Cloud or Firebase project with Firestore enabled (either Firestore Native mode or Datastore mode — this guide covers Native mode)
  • A Google service account with the 'Cloud Datastore User' role or the 'Firebase Admin SDK Administrator Service Agent' role, with a JSON key file downloaded from Google Cloud Console → IAM & Admin → Service Accounts
  • Familiarity with your Firestore data model: collection names, document field names, and any composite indexes required for compound queries
  • A Retool account with permission to create and edit Resources
  • Basic familiarity with Retool's query editor, Table component, and event handler system

Step-by-step guide

1

Create the Firebase Resource for Firestore access

Navigate to the Resources tab in Retool's left navigation. Click Add Resource in the top-right corner. In the resource type picker, find the Google section and click Firebase. In the resource configuration form: - 'Resource name': Enter 'Firestore Production' or a name that identifies the environment. If your team uses separate Firebase projects for development and production, create one resource per project. - 'Service Account Key': Paste the entire contents of your Google service account JSON key file. The JSON block starts with `{ "type": "service_account", "project_id": "...`. Make sure you paste the full JSON — missing curly braces or truncated content will cause authentication failures. Retool encrypts the key immediately on save. - 'Database URL': Enter your Firebase Realtime Database URL (format: https://your-project-id-default-rtdb.firebaseio.com). Even if you are only using Firestore, Retool uses this field to identify the Firebase project. If your project has no Realtime Database, you can use a placeholder URL matching your project ID format. Click Save Changes. Retool will test the service account credentials. If verification succeeds, a green Connected status appears. Note: this single Firebase Resource grants access to all Firebase services for the connected project (Firestore, Realtime Database, Auth, and Cloud Messaging). You select the specific service within each query. You do not need a separate resource for Firestore alone.

Pro tip: For Firestore production access, grant the service account only the 'Cloud Datastore User' role rather than the broader 'Firebase Admin SDK Administrator Service Agent' role. 'Cloud Datastore User' provides Firestore read/write without granting Auth management or Cloud Messaging permissions, following the principle of least privilege.

Expected result: The Firebase Resource appears in the Resources tab with a green Connected indicator. The resource is available in the Retool query editor's resource dropdown.

2

Query a Firestore collection with filters

Open your Retool app and click + New query in the Code panel. Select your Firebase Resource from the dropdown. The query editor shows a Firebase service picker at the top — click the 'Service' dropdown and select 'Firestore'. The action options update to show Firestore-specific operations. From the 'Action type' dropdown, select 'Query collection'. New input fields appear: - 'Collection': Enter the name of your Firestore collection (e.g., 'users', 'orders', 'products'). Collection names are case-sensitive and must match exactly what is in Firestore. - 'Filters': Click 'Add filter' to add WHERE conditions. Each filter has three parts: field name, operator (== != < <= > >= array-contains array-contains-any in not-in), and value. For example: field 'status', operator '==', value `{{ statusFilter.value || 'active' }}`. - 'Order by': Click 'Add order' to set the sort field and direction. Note: Firestore requires an index for any query that filters on one field and orders by a different field — see the Troubleshooting section for index error handling. - 'Limit': Set a maximum document count (e.g., 25). Firestore does not have OFFSET pagination — use cursor-based pagination instead (covered in step 3). Name the query 'getUsers' and set 'Run query on page load' to On. Click Run to test it. Firestore returns documents as an array of objects. Each object includes the document fields plus a special `__id__` field containing the Firestore document ID. Reference the document ID as `{{ getUsers.data[0].__id__ }}` in subsequent queries.

transformer.js
1// Transformer: reshape Firestore query results for display
2// Firestore returns document data with nested fields and Timestamps
3const docs = data;
4return docs.map(doc => ({
5 id: doc.__id__,
6 email: doc.email || '',
7 status: doc.status || 'unknown',
8 created_at: doc.created_at
9 ? new Date(doc.created_at._seconds * 1000).toLocaleDateString()
10 : 'N/A',
11 plan: doc.subscription?.plan || 'free',
12 last_login: doc.last_login
13 ? new Date(doc.last_login._seconds * 1000).toLocaleString()
14 : 'Never'
15}));

Pro tip: Firestore Timestamp fields are returned as objects with _seconds and _nanoseconds properties (e.g., `{ _seconds: 1711929600, _nanoseconds: 0 }`). Always convert them to JavaScript Date objects in a transformer: `new Date(doc.created_at._seconds * 1000)`. If you try to display the raw Timestamp object in a Retool Table, it will show as `[object Object]`.

Expected result: The query returns documents from the Firestore collection as an array. The transformer converts Timestamp fields to readable dates and flattens nested objects. The query result is ready to bind to a Table component.

3

Implement cursor-based pagination for large collections

Firestore does not support OFFSET-based pagination (no SKIP N rows). Instead, Firestore uses document cursors: you pass the last document from the current page as a 'start after' reference, and Firestore returns the next page starting after that document. Retool's Firestore query supports this via the 'Start after document ID' field. Create a state variable to track the pagination cursor. In Retool's State panel (bottom of the canvas), add an Array variable named 'pageHistory' with default value []. This array will store document IDs representing the start of each page, enabling both 'Next' and 'Previous' navigation. Add a second state variable 'currentPageIndex' (Number, default 0). Modify your 'getUsers' query: - In the 'Start after document ID' field, enter: `{{ pageHistory.value[currentPageIndex.value] || '' }}` - An empty string means 'start from the beginning'. Add a 'Next Page' button with this JavaScript event handler: ```javascript // Save the last document ID from current results as the next page cursor const lastDoc = getUsers.data[getUsers.data.length - 1]; if (lastDoc) { pageHistory.setValue([...pageHistory.value, lastDoc.__id__]); currentPageIndex.setValue(currentPageIndex.value + 1); getUsers.trigger(); } ``` Add a 'Previous Page' button: ```javascript if (currentPageIndex.value > 0) { currentPageIndex.setValue(currentPageIndex.value - 1); getUsers.trigger(); } ``` Disable the Previous button when `{{ currentPageIndex.value === 0 }}` and the Next button when `{{ getUsers.data.length < 25 }}` (fewer results than the limit means the last page has been reached).

next_page_handler.js
1// JavaScript event handler: Next Page button
2const lastDoc = getUsers.data[getUsers.data.length - 1];
3if (lastDoc && lastDoc.__id__) {
4 // Append the last document ID as the cursor for the next page
5 pageHistory.setValue([...pageHistory.value, lastDoc.__id__]);
6 currentPageIndex.setValue(currentPageIndex.value + 1);
7 getUsers.trigger();
8}

Pro tip: Reset the pageHistory and currentPageIndex state variables whenever a filter changes (in the filter component's onChange event handler), so that changing a filter always starts pagination from the first page. If you don't reset the cursor, the 'start after' reference may point to a document that no longer matches the new filter, producing unexpected results.

Expected result: The Next and Previous buttons navigate through the Firestore collection 25 documents at a time. The Previous button is disabled on the first page. Changing filters resets to page one.

4

Add Firestore document CRUD operations

With the collection query and pagination in place, add full CRUD operations to make the panel functional for ops teams. **Get a single document**: Create a query with Action 'Get document', Collection `{{ table1.selectedRow.id ? collectionName : '' }}`, and Document ID `{{ table1.selectedRow.id }}`. Name it 'getDocument'. This loads the full document when a row is selected in the Table, including fields not shown in the collection query. **Add a document**: Create a query with Action 'Add document to collection'. Set Collection to your collection name and Document data to a JSON object referencing form inputs: `{ "email": "{{ emailInput.value }}", "status": "{{ statusSelect.value }}", "created_at": {{ new Date() }} }`. Name it 'addDocument'. Wire a 'Create' button's click event to this query, with getUsers triggered on success to refresh the table. **Update a document (partial update)**: Create a query with Action 'Update document'. Set Collection to your collection name, Document ID to `{{ table1.selectedRow.id }}`, and Update data to only the fields being changed: `{ "status": "{{ statusSelect.value }}", "updated_at": {{ new Date() }} }`. The 'Update' action merges changes without overwriting other fields — use 'Set document' if you want to replace the entire document. **Delete a document**: Create a query with Action 'Delete document'. Set Collection and Document ID from the selected row. Name it 'deleteDocument'. Wire a 'Delete' button with a confirmation modal: 'Are you sure you want to delete document {{ table1.selectedRow.id }}? This cannot be undone.' Arrange a Form component on the canvas that loads the selected document's fields when a table row is clicked (bind Form field default values to `{{ getDocument.data.fieldName }}`), and wire the form Submit to the updateDocument query.

update_document.json
1// Firestore update document data — partial field update
2// Only includes fields that have been modified
3{
4 "status": "{{ statusSelect.value }}",
5 "plan": "{{ planSelect.value }}",
6 "notes": "{{ notesInput.value }}",
7 "updated_at": "{{ new Date().toISOString() }}",
8 "updated_by": "{{ current_user.email }}"
9}

Pro tip: Prefer Firestore's 'Update document' action (which merges fields) over 'Set document' (which replaces the entire document) for admin panel edit forms. If an operator only fills in two fields in a form and you use 'Set document', all other document fields are deleted. 'Update document' only modifies the fields you explicitly provide.

Expected result: The CRUD panel is fully functional: clicking a table row loads the document in the detail form, Submit updates specific fields without overwriting others, the Add form creates new documents and refreshes the table, and Delete removes the document after confirmation.

5

Work with Firestore subcollections and nested data

Firestore's hierarchical data model allows subcollections nested under documents (e.g., `users/{userId}/orders/{orderId}`). Retool's Firestore query editor supports subcollection access by specifying the full collection path including parent document IDs. To query a subcollection, set the 'Collection' field to the full path with the parent document ID embedded using Retool template syntax: - Collection: `users/{{ userTable.selectedRow.id }}/orders` This path dynamically changes based on which user is selected in the left-panel table. Create a query named 'getUserOrders' with this collection path, add appropriate filters and ordering, and set 'Run query on page load' to Off. In the userTable's 'Row selection' event handler, trigger getUserOrders so the subcollection query fires when a new user is selected. For deeply nested Firestore documents that contain arrays of embedded objects (e.g., a document with an 'address_history' array of address objects), display the array data in a JSON Viewer component or pre-process it in a transformer: `return data.map(doc => ({ ...doc, address_count: (doc.address_history || []).length, latest_address: doc.address_history?.[0]?.city || 'N/A' }));` For Firestore documents with many nested fields (the document is itself a nested object tree), use a JavaScript transformer to flatten the relevant nested values into top-level properties for cleaner Table display: `return data.map(doc => ({ id: doc.__id__, email: doc.profile?.email, city: doc.profile?.address?.city, plan: doc.subscription?.plan }));` For complex Firestore data models involving collection group queries, transactions, and batch writes across multiple documents, RapidDev's team can help design and implement the full Retool architecture.

transformer.js
1// Transformer: flatten nested Firestore document structure
2// Input: Firestore documents with nested profile and subscription objects
3const docs = data;
4return docs.map(doc => ({
5 id: doc.__id__,
6 email: doc.profile?.email || doc.email || '',
7 display_name: doc.profile?.displayName || '',
8 city: doc.profile?.address?.city || '',
9 country: doc.profile?.address?.country || '',
10 plan: doc.subscription?.plan || 'free',
11 plan_expires: doc.subscription?.expires_at
12 ? new Date(doc.subscription.expires_at._seconds * 1000).toLocaleDateString()
13 : 'N/A',
14 tags: Array.isArray(doc.tags) ? doc.tags.join(', ') : '',
15 doc_count: Array.isArray(doc.orders) ? doc.orders.length : 0
16}));

Pro tip: Firestore collection group queries (querying across all subcollections with the same name, regardless of parent document) are not directly supported by Retool's Firebase query editor action picker. For collection group queries, use a REST API Resource targeting the Firestore REST API (https://firestore.googleapis.com/v1/projects/{project}/databases/(default)/documents:runQuery) with the `from.allDescendants: true` flag.

Expected result: Selecting a user in the left panel table triggers the subcollection query and populates the right panel with that user's orders. Nested document fields are flattened by the transformer and display correctly in Table columns.

Common use cases

Build a Firestore document search and edit panel for support teams

Create a Retool app where support agents can search a Firestore collection by multiple field values (user ID, email, status), view the matching documents in a Table, click a row to load the full document details in a form panel, and submit edits that update specific fields without overwriting the entire document. The panel shows Firestore document IDs alongside data and allows adding new documents through a create form.

Retool Prompt

Build a Retool Firestore admin panel with search inputs for email and status fields, a Table showing query results with document ID, created_at, and key user fields, a detail Form on the right that loads the selected document's fields, and Save and Delete buttons that trigger Firestore update and delete queries.

Copy this prompt to try it in Retool

Build a paginated Firestore collection browser with compound filters

Create a Retool data browser for a high-volume Firestore collection (orders, events, audit logs) that supports compound WHERE filters, ordering by timestamp, and cursor-based pagination that loads the next page starting after the last document of the current page. Operators can filter by date range, status, and user segment, and export the current page as CSV.

Retool Prompt

Build a Retool Firestore collection browser with date range, status Select, and user ID filters, a Table showing paginated results with 25 documents per page, Next and Previous page buttons using Firestore cursor-based pagination, and a CSV export button for the current results.

Copy this prompt to try it in Retool

Build a Firestore subcollection explorer for nested data management

Create a Retool two-panel app where the left panel shows a parent collection (e.g., 'users'), and selecting a user on the left loads that user's subcollection (e.g., 'orders' or 'notifications') in the right panel. Operations staff can drill into nested Firestore data, edit subcollection documents, and add or delete subcollection entries without navigating the Google Cloud Console.

Retool Prompt

Build a Retool Firestore explorer with a users Table on the left bound to the 'users' collection, and when a user row is selected, a second Table on the right loads that user's 'orders' subcollection documents. Include an Add Order form and Delete button for the subcollection.

Copy this prompt to try it in Retool

Troubleshooting

Firestore query returns 'The query requires an index' or 'FAILED_PRECONDITION: The query requires an index'

Cause: Firestore requires composite indexes for queries that filter on one field and order by a different field, or for queries with multiple inequality filters. Without the index, Firestore refuses to execute the query.

Solution: The error message includes a direct link to create the required index in the Firebase Console — click it to auto-populate the index creation form. Index creation takes 1-5 minutes for small collections and longer for large ones. While the index is building, the query will continue to return this error. If you do not have access to the Firebase Console, share the index creation link with your Firebase project administrator. For Retool development, simplify the query to single-field filters until the composite index is ready.

Firestore Timestamp fields display as '[object Object]' in Retool Tables

Cause: Retool's Firestore connector returns Timestamp values as JavaScript objects with `_seconds` and `_nanoseconds` properties rather than as Date strings. The Table component does not know how to display plain objects.

Solution: Add a query transformer that converts all Timestamp fields to readable strings. Use `new Date(doc.created_at._seconds * 1000).toLocaleDateString()` to convert each Timestamp to a human-readable date. Apply this pattern to all Timestamp fields in your Firestore documents. If you are unsure which fields are Timestamps, check the raw query response in Retool's query editor Result tab — Timestamp objects are identifiable by the `{ _seconds: ..., _nanoseconds: ... }` structure.

typescript
1// Fix for Firestore Timestamp display in transformer
2const docs = data;
3return docs.map(doc => ({
4 ...doc,
5 // Convert all known Timestamp fields
6 created_at: doc.created_at?._seconds
7 ? new Date(doc.created_at._seconds * 1000).toLocaleDateString()
8 : 'N/A',
9 updated_at: doc.updated_at?._seconds
10 ? new Date(doc.updated_at._seconds * 1000).toLocaleString()
11 : 'N/A'
12}));

Firestore 'Get document' query returns empty data for a document ID that exists in the Firebase Console

Cause: The document ID string contains extra whitespace, or the collection path has a typo. Firestore document IDs are exact-match — a trailing space or wrong case returns empty rather than an error.

Solution: Add .trim() to the document ID expression in your Retool query: `{{ table1.selectedRow.id.trim() }}`. Verify the collection name matches exactly (Firestore collection names are case-sensitive). Check that the document ID is not composite (some Firestore document IDs are generated as paths including slashes — in that case, you need the collection path AND the document ID separately).

Firebase Resource connection fails with 'Error: Could not refresh access token' after previously working

Cause: The Google service account JSON key has been revoked or deactivated in Google Cloud Console, or the key was deleted as part of a security rotation. Service account keys do not expire automatically but can be revoked at any time by Google Cloud administrators.

Solution: Go to Google Cloud Console → IAM & Admin → Service Accounts → select your service account → Keys tab. Check whether the key ID from your JSON file shows as Active. If it is Disabled or Deleted, create a new key, download the JSON, and update the Retool Firebase Resource with the new key contents. If the key still shows as Active, verify the service account itself has not had its Firestore permissions revoked in the IAM Permissions tab.

Best practices

  • Use the 'Update document' action (not 'Set document') in Retool for admin panel edit forms. 'Set document' replaces the entire document including fields not shown in your form, causing data loss for fields outside the form scope.
  • Always include a `created_at` and `updated_at` field in documents written from Retool, set to `{{ new Date().toISOString() }}`. Firestore does not automatically track modification timestamps — without them, audit and debugging capabilities are severely limited.
  • Convert all Firestore Timestamp fields in a query transformer rather than in individual component expressions. A centralized transformer keeps your component bindings clean and ensures consistent date formatting across the entire app.
  • Store the Google service account JSON key as a Retool Configuration Variable (Settings → Configuration Variables) marked as secret rather than pasting it directly into the resource settings. This prevents the key from being visible to non-admin Retool users who view the resource configuration.
  • Create Firestore composite indexes proactively for all filter-plus-sort combinations your Retool queries use. Index creation takes time — building indexes before go-live prevents query failures in production. Maintain a list of required indexes in your Firebase project documentation.
  • Use Firestore's 'Query collection' action with explicit Limit values in all Retool queries. Without a Limit, Firestore returns all matching documents — on large collections this can exceed Retool's 100MB response size limit and cause timeouts.
  • Implement optimistic UI updates in Retool: after a successful Firestore write, immediately update the local state variable that drives the Table rather than waiting for a full collection re-query. This makes the UI feel fast even on slower Firestore connections.
  • For Retool apps used in production by multiple concurrent operators, add a 'Last modified by' field to every document write that captures `current_user.email`. This creates a lightweight audit trail within Firestore itself without requiring a separate audit log collection.

Alternatives

Frequently asked questions

Can Retool run Firestore collection group queries (querying all subcollections with the same name)?

Retool's native Firestore action picker does not expose collection group queries directly. As a workaround, use a generic REST API Resource targeting the Firestore REST API endpoint https://firestore.googleapis.com/v1/projects/{projectId}/databases/(default)/documents:runQuery with a structured query body that sets `from.allDescendants: true`. You will need to configure Bearer Token authentication using a short-lived OAuth token generated from your service account.

Does Retool support Firestore real-time listeners?

Retool apps do not support Firestore real-time listeners (onSnapshot). Retool queries are request/response — they return data at the moment the query runs and do not maintain a live connection. To approximate real-time updates, enable Retool's built-in query polling by setting 'Refresh interval' on the collection query (e.g., every 5 seconds). This periodically re-fetches the collection, catching recent changes at the cost of additional Firestore read operations.

How do I write Firestore batch operations from Retool?

Retool's native Firestore action picker does not include batch write operations. For batch updates across multiple documents, use a JavaScript query in Retool that calls multiple Firestore queries in sequence: `await updateDoc1.trigger(); await updateDoc2.trigger();`. For true atomic Firestore transactions, use a Retool Workflow that calls a Cloud Function or a serverless endpoint that executes the Firestore transaction server-side.

Can I use Retool's Firestore Resource with Firestore in Datastore mode?

Retool's Firebase connector targets Firestore in Native mode, which uses the Cloud Firestore API. Firestore in Datastore mode uses the Cloud Datastore API — a separate API with different endpoints and query syntax. Retool does not have a native Datastore mode connector. If you are using Firestore in Datastore mode, connect via a REST API Resource targeting the Cloud Datastore REST API (https://datastore.googleapis.com/v1/) with OAuth 2.0 authentication.

What is the maximum number of documents Retool can retrieve from Firestore in one query?

Retool's Firestore queries are subject to the platform's 100MB response size limit. In practice, you should set a Limit of 25-100 documents per query and use cursor-based pagination for larger datasets. Retrieving thousands of documents in a single Retool query is not recommended — it strains Retool's response handling, inflates Firestore read costs (each document returned counts as one read), and makes the UI unresponsive.

How do I filter Firestore documents by a date range in Retool?

Firestore stores dates as Timestamp objects. To filter by date range, pass JavaScript Date objects or ISO strings as the filter values — Retool's Firestore connector handles the conversion to Firestore Timestamps automatically. In the query filter, set field 'created_at', operator '>=', value `{{ new Date(dateFilter.startDate) }}` and add a second filter for the end date with '<='. Ensure a composite index exists on the 'created_at' field for range queries to execute efficiently.

RapidDev

Talk to an Expert

Our team has built 600+ apps. Get personalized help with your project.

Book a free consultation

Integrations are where projects stall

We wire Retool integrations like this one every week — auth, webhooks, edge cases included. Fixed-price builds from $13K, delivered in 6–10 weeks.

Talk to an integration engineer

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.