Firebase & Supabase

Your FlutterFlow app almost certainly requires a backend, and although plenty of options for this exist, the two most popular and well integrated with FlutterFlow are Firebase and Supabase.

This can be a confusing choice for new FlutterFlow users; especially those who don't come from a software engineering background. Initially, you'll find Firebase integrates more deeply with FlutterFlow, so it'll probably seem like the most appealing or simplest option. But before long you'll hear or experience these common complaints about Firebase:

  • Firebase will vendor-lock you!
  • Firebase suddenly gets really expensive as your app starts to scale 💸
  • Firebase doesn't use a relational database 🛢️

Firebase does indeed hold you hostage once you create your infrastructure on the platform, and in fact, Supabase's initial business model was to offer a Firebase alternative to combat this vendor-lock.

However, there's an important distinction here: Firebase is not a database, it's a whole suite of services, many of which are extremely useful in app development. When people compare "Firebase" to Supabase, they're often actually comparing Firestore to Supabase, which is the database that FlutterFlow integrates with.

This database, Firestore, is indeed vendor-lock central, and migrating away from it can be difficult and expensive. However, Firebase is much more than just its database offering.

Firebase Auth, cloud messaging, storage, app check, app configurations, crashlytics, dynamic links, and many other services are all ready to go, with great FlutterFlow integration, and it's for this reason that I advocate the use of Firebase with one caveat – I don't recommend Firestore as a primary database. But more about that later. Right now, let's dive in and set Firebase up.

Firebase setup: Actions to take

In the "Firebase" section of the "App Settings" menu in FlutterFlow, there's the option to connect to an existing Firebase project or create a new one. This is covered in the documentation.

There's a small choice to make here: because the Starter Kit is a cloned project, the URL will be "the-flutterflow-starter-xxxx", which is inconvenient because your new Firebase Project ID will also bear a similar name and cannot be changed later. To avoid this, you can set up the new Firebase project in the Firebase console and connect it to FlutterFlow. The trade off is that this won't work as seamlessly, because FlutterFlow does tasks like setting up the authorized users and the GCP service accounts.

However, the Firebase ID of a project is not externally visible and doesn't matter a great deal. If you're okay with this naming convention, you can tell FlutterFlow to create the project for you, and the setup will be simpler.

Once your project is created, upgrade to the Blaze plan in Firebase. Then turn on Authentication. The Starter kit expects email/password, Google, and Apple sign-in, so turn these on. You can easily remove or add providers later.

Turn on Firebase storage, and then back in Flutterflow, hit the deploy button for Storage.

You can now generate the config files. Be sure to double check that you have changed the project name and bundle id.

Next we'll set up Firestore. Even though Supabase will be our main database, Firestore still has an important job – it permits Flutterflow to seamlessly manage user records and automatically save user device tokens. This way, push notifications will work without needing modification.

Ensure that the user schema is present, and that the "Users Collection" field is set to "users" in the dropdown, then click all the buttons for validating and deploying the indexes, references, and rules. Note that if you start the database in Firestore's test mode, you must set up the rules properly before moving to production. In case of any deploy failures, sometimes you can simply try one more time before diving into debugging the failure.

Next is push notifications, just head over the Push Notifications section in Flutterflow and hit deploy.

And finally, there's a cloud function called mintSupabaseToken and a custom action called updateSupabaseToken. Hit deploy for the function, and the validation button for the action. If you open the issues pane, the warnings can easily bring you to where you need to go.

At this point, all warnings and errors should be cleared and you should have green ticks across the board.

Supabase is also a suite of services, but it's far more focused on its database offering. They use PostgreSQL under the hood, which is one of the most established and bulletproof databases in the world. The really nice thing about this database is that, unlike Firestore, it is relational, which means that you can easily connect data housed in different tables across your app, among numerous other advantages.

The FlutterFlow Starter Kit is designed to make the best use of both Firebase and Supabase at the same time. It keeps all the Firebase services like Authentication, login flows, push notifications, etc, but it also does some magic under the hood that allows the Auth service to authenticate with Supabase, allowing for the flexibility, portability, and lower operating costs that Supabase provides.

Supabase setup: Actions to take

The starter kit has a pre-defined database schema for Supabase, with tables for notifications, users, app-compliance (privacy policy and terms of service) and an administrator role.

If you haven't already, sign-in to Supabase and create a project. It's recommended to create at least two projects, one for dev and one for prod.

The easiest way to load up the Starter Kit schema is by simply copy-pasting the SQL into the SQL editor in the Supabase dashboard. If you prefer, you can use a migration.

Copy the SQL from this page, paste it into the SQL editor, and click "Run":

Now we just need to grab the Supabase URL and Supabase Anon key from the Supabase dashboard (Project Settings > API > Project API Keys) and paste it into Flutterflow. For the Starter Kit, there are two places to do this. First go to Integrations > Supabase and paste the URL and Anon key there, followed by clicking "Get Schema".

Next, go to "Dev Environments" and populate "supabaseUrl" and "supabaseAnonKey". (There's also an optional "apiBaseUrl", we'll get to this later.)

Supabase & Firebase: Actions to take

At this point, most of the code to reconcile Supabase with Firebase is ready to go. You'll have deployed a Firebase function called "mintSupabaseToken" in the Cloud Functions section of the FlutterFlow console. There's just one more step.

The Firebase function needs the JWT secret from your Supabase project, as it requires this privilege to securely mint the tokens. In your Supabase dashboard, navigate to Project Settings > API > JWT Settings and copy the JWT Secret. Since the "mintSupabaseToken" function should already be deployed, go to your Google Cloud Platform console and make sure the right project is selected. Click on the newly created "mintSupabaseToken" function, then "Edit". Under "Runtime, build, connections, and security settings", there's a section called "Runtime environment variables". Add a new variable called SUPABASE_JWT_SECRET and paste the value you got from your Supabase dashboard. Then redeploy the function. Now you may use Supabase in your FlutterFlow app alongside Firebase Auth.

And now you may run the debug mode in Flutterflow and the application should work. Don't forget that if signing in with Google, you'll also need to add the Authorized domain as explained here in the docs.

If you have any issues (like issues logging in), it could be related to the mintSupabaseToken function. Open the debug console in test mode, and check if this function is appearing in red. The console will log statements from the function for certain errors, while the network tab will show the request and response. Note that sometimes the error might show as a CORS issue – that's probably a red-herring. It's more likely that function is just failing becuase something is misconfigured. Be sure the SUPABASE_JWT_SECRET environment variable has been set in GCP, as described above.

Here's a video to help with troubleshooting:

The technical stuff (optional read)

Supabase has Row Level Security, a powerful feature that allows granular access to data at the row level. This access is based on an Authorization token, known as a JWT, which is passed from your user's device to Supabase. However, Firebase Auth tokens and Supabase tokens, despite both being JWTs, are minted in different places and so Supabase won't accept a Firebase Auth token.

Supabase are actually working on implementing this, but even so, FlutterFlow may or may not implement the code that is needed to take advantage of it when it comes out.

So how do you send a Supabase token from an app that uses Firebase Auth? The generally accepted solution thus far has been that you can't, and you must switch to Supabase Auth, thus breaking all the firebase features that rely on Firebase Auth. But I found a different solution: mint Supabase tokens on the fly.

I've added some code to main.dart which calls the custom action "updateSupabaseToken". This is a watcher; it monitors the Firebase token and whenever it changes (such as during a token refresh), it calls "mintSupabaseToken". This returns the Supabase token and sets it into the Authorization header, allowing the Supabase calls to succeed.

An important point to bear in mind for this to work is that we'll now have two users tables, one in Supabase and one in Firebase in addition to the record in Firebase Auth. These tables need to be kept in sync, so it's important to set actions in the login/register flow that update the users table in Supabase (the Starter Kit's auth flow already does this, but it's important to be aware of it).

Although maintaining the two users tables is slightly more work, it actually creates some really nice benefits. For instance, if you want any user to be able to see and interact with only certain data about other users in your app, or for users to update data about themselves while being unable to update their own subscription status or admin privileges, you can use the firestore database for important flags like is_admin or is_paid.