Firebase / Firestore Integration
How to build internal tools on top of Firebase data
Retool supports Firebase's Admin API and allows you to build fast and useful CRUD apps on top of Firestore, RealtimeDB, and Auth data.
Setting up Firebase
To build on top of your Firebase data in Retool, you'll need to (gasp) connect your Firebase setup. Once you've created your Retool account, head over to the Resources tab, click the "create new" button, and select Firebase from the "APIs" section.
Here's what you'll need to provide:
- Your Firebase Database URL
Go to your Firebase console, and then select your project. Click Realtime Database on the left side bar. This will expose a URL in the form https://you-project-id.firebaseio.com/
, which is what you need to copy into Retool.
- Your Firestore Project ID
Head back to your Firebase console project settings, and copy the Project ID. It should look something like retool-522x1
.
Database URL or Project ID
You must provide either the Firebase Database URL or the Firestore Project ID. You do not need to provide both.
- Your Service Account Key
If you're already in your Firebase console (you should be, if you're following along), you can click on your project settings pane, choose the "service accounts" tab, click on the "Firebase Admin SDK" icon, and then click the "Generate new private key" button. That should download a JSON blob that you can paste directly into Retool.
If this didn't work, you can also create an account key from your Google Cloud Platform console. Go there, select a project, hit Create Service Account, name it whatever you want, then in "role" select Firebase Admin. Click continue, then Create Key, then choose the JSON one. It'll download and then you can copy it into Retool.
Your configuration should resemble something like this:
After providing the Service Account Key and the Database URL, hit "Save." Retool will validate that it's able to connect to your Firebase database and then you're ready to go.
Working with the RealtimeDB
Querying the database
The query the RealtimeDB, create a new query (+new in the bottom panel), and select your Firebase resource from the resource dropdown. Then, choose "Database (read / write)" from the service type dropdown and select "Query database" as the action type.
All you need to query the DB is a Database ref. In our setup, we have top level refs for songs
and blogPosts
, so we can use either of those in our query.
If you click the "use ordering" checkbox, you'll get access to a bunch of different ordering and filtering options:
- Ordering by child, key, or value
- Limiting to first or last
- Using
equalTo
,startAt
, andendAt
to filter your queries
We recognize this is a bit confusing, as not all of these options actually relate to ordering - we'll be fixing this soon!
Setting and updating data
The Firebase database offers two different APIs for updating data:
.set()
The .set()
method replaces your chosen object with the value that you provide. So if your object has 5 fields, and you use .set()
and pass something with 3 fields, it will now have 3 fields. To use this method in Retool, just choose "Set" from the action type dropdown. You'll need to provide a database ref and an object to set.
.update()
The .update()
method updates fields if they exist, but doesn't replace an entire object. The query format is the same as .set()
- just choose "Update data" from the action type dropdown and provide your database ref and update object.
You cannot set or update root level properties
Retool doesn't allow you to run
.update()
or.set()
queries on the root of your database - if your database ref doesn't have a/
in it (i.e. references a child), you'll get an error on your query. If you input a database ref but still get this error, you might need to try prepending it with a slash (e.g./characters
instead ofcharacters
).
Deleting fields
Retool doesn't support deleting fields or objects directly, but you can get it done via the set()
method. If you .set()
a property to null, it will get deleted from the database - just keep in mind that you'll need to pass that null
as JavaScript in curly braces ({{ }}
). Here's an example of how we'd delete the "title" property from our songs
object:
Working with Firestore
All of these examples will use Firestore, but the same concepts apply to the RealtimeDB if you’re using that. Once you’ve connected your Firebase account to Retool (see previous section), you can get started querying Firestore right away.
Firestore Admin Panel Template
If you're looking to do basic CRUD on your Firestore data, we built a template that can help you get started. You can see more details here, or click "create new" and then "create from template" on your Retool homepage. Look for the Firestore Admin Panel template to get building.
Querying Firestore and getting Collections
To query Firestore, create a new query (+new in the bottom panel), select “Firestore” from the service type dropdown, and choose “Query Firestore” as the action type. You can specify a collection via the dropdown, or input a custom value by clicking “Use raw id.”
The Retool GUI lets you query via a key (a document field), an operator (==
, in
, >=
, etc.), and a value. You can add as many statements as you want, add a limit, an order by field, and other fun stuff too.
You can set the value of your key or value dynamically with JS, which you can use to implement search (or anything else). If we have a TextInput component, we can pass the value of that component into our “value” input. Here, we’ve added a TextInput component that allows the user to input a value for the “boro” field in our ny-facilities
collection, and we’ve passing the value of that component into the “value” field in our Firestore query. Voila, search!
To get a birds-eye view of all of the documents in a collection, we can pull the results of this Firestore query into a Table component. The way our collections are formatted makes this as simple as referencing {{ listDocuments.data }}
in our table’s “data” field, but your Firestore data might be organized differently.
Formatting Firestore query results for a Table component
The results of your Firestore query might not be in the right format for displaying in a table off the bat. You can write JavaScript anywhere in Retool, so don't fret!
To display the data in a Table, you'll probably want to use something like this:
{{ _.values(firebaseQuery.data) }}
Or in more advanced cases where you would also like to pull in the Firebase key:
_.zipWith(_.keys(firebaseQuery.data), _.values(firebaseQuery.data), (key, value) => Object.assign({}, { key: key}, value))
You can write this actual code in the "data" field for your table, apply it as a Transformer in your query, or create a standalone Transformer tool. Check out our Transformer docs for more info.
You can make use of helper functions like formatDataAsArray
, but you might need to do some custom formatting work.
Retool also allows you to query available Firestore collections (top level or under a specific document ID), which we can use to dynamically select which documents you want to display in this table. To get started, we’ll create a new query that lists top level collections:
If we wanted to query a nested collection, we could specify the parent document ID in the provided form field. We could also specify the document path, like home/locations/restaurants
. We also support collection group queries!
Now let’s pull the results of this query into a Dropdown component, and connect the value of that component to our original listDocuments
query. Make sure that your listDocuments
query is set to run when inputs change, not when manually triggered (scroll to the bottom of the “General” tab).
We’re populating the dropdown with the .data
property of our new listCollections
query, and then passing the dropdown’s value into the “Collection ID” field of our listDocuments
query (after selecting “Use raw id”). Now, selecting a new collection from the dropdown will update the table to show that selected collection’s documents. You can set custom display values, placeholder text, and an initial value (among other things) by clicking on the dropdown and using the righthand panel.
Changes to timestamp fields in Firestore
When you query Firestore, Retool returns any timestamp fields as ISO formatted strings. Recently, after we upgraded our Firebase Admin SDK version, this changed and timestamp fields were being sent as objects (with keys for
_seconds
and_nanoseconds
) for a few days, but we've reverted this change to keep persistent behavior.
Updating documents in Firestore
To update a Firestore document in Retool, you just need the document’s ID and the object you’d like to update it with. Create a new query (+new in the bottom panel), select your Firebase resource, choose “Firestore” from the service type dropdown, and choose “Update Document” as the action type.
You’ll need to specify a document ID and a value. For the “value” field, passing a partial object will update only your passed fields, and passing current values (e.g. fields with the same values as current) works too.
There are a few ways you might want to implement the UI here:
- A Form component with TextInput components for each document field you want to update
- A JSON Editor component that lets you update values directly, and submit the changed value with a button
- Making the Table component editable
You can choose whichever pattern best fits your users’ needs - as long as you format the results as a JSON object and pass it in the “value” field of your query, you should be good to go. Keep in mind that both of these fields - document ID and value - can be dynamic, so you can pass pretty much anything to them via JS in {{ }}
. Here, we’ll walk through how to edit document values directly through the Table component.
In the previous section, we built a table that pulls documents from the collection specific in a dropdown on top of our table. We can make this table editable, and connect it to our update query on the backend to update individual fields, or multiple fields all together. Start by clicking on the table, heading over to the “columns” section in the right sidebar, and updating each individual column (or whichever ones you want) to be editable.
This just makes them editable on the frontend - we still need to connect our updateDocument
query so that editing actually does something. First, we’ll head into our updateDocument
query and fill in the right values for document ID and value.
For document ID, we’ll choose the currently selected row in the table with {{ table.selectedRow.data._id }}
. We’ll also click “Use raw id” for our collection, and pass the selected value from the dropdown (you can also hardcode this, of course).
If you click on a different row in the your table, you’ll notice the preview for document ID change. If you’ve enabled your table to select multiple rows, this won’t work - you’ll need to parse that with JavaScript or run a loop (more on that later).
For the value field, we’ll access a special property of our table that only exists when a row (or rows) have been updated: .recordUpdates
. The .recordUpdates
property is an array of objects, one per edited row, with the values for each field in that row, including the edited ones. If you’ve only edited one row, the .recordUpdates
array will have one object, and so on and so forth. Keep in mind that .recordUpdates
will contain values for every field in your table, regardless of whether its been edited or not - this makes it really easy to pass into our updateDocument
query, because Firestore ignores fields that haven’t changed.
Here’s we’ve edited two cells in the second row. That means table.recordUpdates
will have one element, with all of the values for the second row in our table (including the two edited cells). We can index that array and pass it straight into the value field in our updateDocument
query.
The last thing we need to do is connect this query to the table: in the table settings on the right, scroll down to the “table edit queries” section, and select the updateDocument
query from “bulk update action” dropdown. We’ve now got an editable table - you can update the data types of any of these columns (to use a date picker, for example), change column names, and do all sorts of useful table things.
There’s only one caveat - this won’t work if you update multiple rows in your table at once. To do that, you’ll need to create another query to iterate through all of the rows (i.e. records) in .recordUpdates
and apply the updateDocument
query to each one. Here’s how we’ll do that:
- Create a JS code query to trigger our
updateDocument
query for each updated record
Create a new query (+new in the bottom panel) and choose “Run JS Code” from the resource dropdown. This lets us run JavaScript as a query, and we don’t need to use {{ }}
to reference the rest of our app. Each Retool query has a .trigger()
method - what we’re going to do is iterate through the .recordUpdates
array and trigger our updateDocument
query for each element. Here’s the boilerplate:
const toUpdate = table.recordUpdates;
for (let record of toUpdate) {
updateDocument.trigger();
}
The issue is that our current values for document ID and value in our updateDocument
query won’t work here - we’re pulling the document ID from the currently selected table row, and we’ve hard indexed [0]
for .recordUpdates
in the value field.
- Pass additional scope into the
updateDocument
trigger to get the document ID and value for each query run
When triggering queries in Retool via a Run JS Code query, you can pass additional scope - and that’s what we’ll do for our updateDocument
query. In our Run JS Code query, we’ll update our code to pass the record’s _id
as document_id
, and the entire record object as document_object
. Then, we’ll reference those values in the updateDocument
query.
const toUpdate = table.recordUpdates;
for (let record of toUpdate) {
updateDocument.trigger({
additionalScope: {
document_id: record._id,
document_object: record,
},
});
}
- Update the
updateDocument
query to use the additional scope
Now that we’re passing a document_id
and a document_object
via our Run JS Code query, let’s update those references in the updateDocument
query. You’ll notice that these variable names will throw errors, since they don’t really exist yet - they get defined when the JS Code query runs, and get passed in there.
This is what the final JS Code query should look like:
- Update our table to use the
handleUpdates
query
The last thing we need to do to make this work is change the bulk update query connected to our table from updateDocument
to handleUpdates
.
A couple of ergonomic tweaks that will make this a bit smoother:
- Run the
listDocuments
query when thehandleUpdates
query succeeds, so our table refreshes - Disable query success notifications for
updateDocument
so our screen doesn’t get too crowded
If you run into any issues, you can always console.log
out into your browser console to help debug.
Writing timestamp fields back to Firestore
To write back to timestamp fields in Firestore (and avoid setting them as strings), you'll need to convert your data to a date type. Retool ships with
moment.js
pre-installed, so you can write something like{ lastUpdatedAt: {{ moment() }} }
to keep your timestamps formatted properly. You can also use the old reliablenew Date()
. Currently, you'll need to write these timestamps directly in your update or insert query - if you pass them through a JS Code query as additional scope, they'll get converted into strings. We'll hopefully have this fixed soon!
Using refs in Firestore queries
If your Firestore query needs to make use of a Database Ref, there's a special way to specify that in Retool: with the $ref
identifier in the value of your query.
The syntax is inspired by the MongoDB Extended JSON syntax, where you use $ref
to specify that the string should be a ref.
Inserting a document
In your query editor, select your Firebase resource from the resource dropdown, choose “Firestore” as the service type, and “Insert document” as the action type. Inserting a document in Retool takes the same format as updating a document, but you can leave the document ID blank if you want Firestore to automatically generate one.
Deleting a document
In your query editor, select your Firebase resource from the resource dropdown, choose “Firestore” as the service type, and “Delete document” as the action type. The only parameter you need to provide is a document ID - here, we’re setting it dynamically by referencing the _id
property of the currently selected document in our table.
Getting document by ID
In your query editor, select your Firebase resource from the resource dropdown, choose “Firestore” as the service type, and “Get Document by ID” as the action type. The only parameter you need to provide is a document ID - here, we’re setting it dynamically by referencing the _id
property of the currently selected document in our table.
Working with Firebase Auth
Retool lets you view, update, add, and delete users from your Firebase Authentication setup.
Firebase Auth Admin Panel Template
If you're looking to do basic CRUD on your Firebase Auth data, we built a template that can help you get started. You can see more details here, or click "create new" and then "create from template" on your Retool homepage. Look for the Firebase Auth Admin Panel template to get building.
Listing users
To list your existing Firebase Auth users, create a new query (+new in the bottom panel), choose your Firebase resource from the resource dropdown, select “Auth (user management)” from the service type dropdown, and pick “List users” as the action type. We’ve displayed the results in a Table component via {{ listUsers.data.users }}
.
In the listUsers
query, we can set a limit for the number of users we want our query to return. The limit value (and pretty much anything in Retool) can also by dynamic, so we can hook it up to a text input that lets you set a limit for the query in your app.
Once we’ve pulled our users into our table, we can apply frontend filters via the little filter icon.
Getting a user by UID, email, or phone number
To get a user by UID, email, or phone, create a new query (+new in the bottom panel), choose your Firebase resource from the resource dropdown, select “Auth (user management)” from the service type dropdown, and pick “Get user by email/UID/phone” as the action type. Here’s an example using a user’s email - we’ve added a TextInput component up top, and are passing its value into the user email field in our query.
Deleting a user
To delete a user, create a new query (+new in the bottom panel), choose your Firebase resource from the resource dropdown, select “Auth (user management)” from the service type dropdown, and pick “Delete a user” as the action type. You’ll need to pass in the User UID of the user you want to delete - here, we’re pulling it from the currently selected table row (this table is populated with our listUsers
query.
Updating a user’s information
To update a user’s information, create a new query (+new in the bottom panel), choose your Firebase resource from the resource dropdown, select “Auth (user management)” from the service type dropdown, and pick “Update a user” as the action type. You’ll need two things: (1) the user’s UID, and (2) the update object.
There are a bunch of ways you can design a UI for updating user information - a form, a raw JSON object, and even an editable table. If you’re looking to make your table editable, follow the steps outlined above for doing the same with Firestore.
A common use case for Firebase Auth in Retool is verifying email addresses. We can use this updateUser
query we built to do just that. We’ll start by dragging a button under our table, and updating its label to “Verify Email.” Then, let’s hook up that button to run our updateUser
query. Finally, we’ll set the User UID field in the query to the currently selected table row, and pass in {
"``emailVerified``"``: true }
as the user object.
We’ve also configured the updateUser
query to trigger listUsers
on success - now, clicking the Verify Email button updates the user’s emailVerified
field, and re-renders the table.
Creating a new user
To create a new user, create a new query (+new in the bottom panel), choose your Firebase resource from the resource dropdown, select “Auth (user management)” from the service type dropdown, and pick “Create a user” as the action type. You’ll need to provide a user object. Now technically the Firebase API doesn’t require you to include any fields at all in that object, but you’ll usually want to pass an email and password at least.
Keep in mind that the user object field is dynamic, so you can pass values from other Retool components (a form, two text inputs, etc.) as you build your UI.
Ask the community about Firebase/Firestore
If you're running into any issues with Firebase/Firestore in Retool, check out some of the answered questions on our community forums, or ask one of your own.
Updated 20 days ago