The New Default. Your hub for building smart, fast, and sustainable AI software

See now
Illustration of a Supabase database connected to a Flutter mobile app showing a launching rocket

Getting Started with Supabase and Flutter: From Setup to Production

Marcin Wróblewski
|   Updated Oct 18, 2026

Supabase gives a Flutter app a hosted PostgreSQL database, authentication, file storage and real-time updates, all reachable from the supabase_flutter package without writing a server.

For teams whose bottleneck is backend capacity, that lets one Flutter developer ship features end to end.

This guide walks through the setup, then covers the security and configuration steps a production app needs. All code targets supabase_flutter v2 (2.18.3 at the time of writing).

Executive Summary

Supabase is an open-source backend platform built on PostgreSQL that covers authentication, storage, real-time subscriptions and serverless Edge Functions, so many Flutter apps can launch without a custom backend.

The trade-off is that security moves into the database: Row Level Security (RLS) policies, not an API layer, decide who can read what.

Teams that enable RLS on every table, keep secret keys out of the app binary and test deep links on each platform get a production backend without building one. Skip the first step and you ship a publicly writable database.

Why Do Flutter Teams Choose Supabase as a Backend?

Flutter teams choose Supabase because it removes the backend as a dependency for features: auth, a relational database and file storage are ready the day the project is created, which matters most when you're racing to launch an MVP. Flutter already lets one team ship iOS and Android from a single codebase. That's how we built a bilingual real estate investment app for both platforms in under 12 weeks. With Supabase, a Flutter developer can change the schema with a SQL migration instead of waiting for a backend release.

The relational model is the main difference from Firebase's Firestore. If your data has real relationships (users, organizations, orders, permissions), Postgres joins and constraints save you from the denormalization work a document store pushes onto the client. Supabase also supports a long list of auth providers, including Google, Apple, Azure, GitHub and Slack.

It isn't free of trade-offs. You still need someone comfortable with SQL and RLS, and heavy custom server logic ends up in Edge Functions or a separate service anyway.

How Do You Set Up a Supabase Project for a Flutter App?

Create a project in the Supabase dashboard, pick the region closest to your users (it's where your database, auth and storage will live), and then create your tables.

Creating a Table with Row Level Security in the SQL Editor

Open the SQL Editor and run a script like the one below. Tables created with SQL have RLS disabled by default, which makes them readable and writable by anyone holding your app's public key. Enable it in the same script:

create table colors (
  id serial primary key,
  name varchar(255) not null
);

alter table colors enable row level security;

-- Allow anyone using the app to read colors (but not write them)
create policy "Colors are readable by everyone"
  on colors for select
  using (true);

insert into colors (name) values ('red'), ('blue'), ('green');

With RLS enabled and no policy, the table returns nothing through the API. That is the safe default, and it's why a query that "worked in the dashboard" can return an empty list in the app. See the Supabase RLS guide for policies scoped to the signed-in user.

Configuring Authentication Redirects

Under Authentication > URL Configuration, add your app's redirect URL (for example, io.supabase.flutter://login-callback/). Email confirmation, magic links and OAuth all send users back to this URL, so it has to match the deep link scheme you configure later.

How Do You Connect a Flutter App to Supabase?

Add the package to pubspec.yaml:

dependencies:
  supabase_flutter: ^2.18.3

Initialize Supabase before runApp() with your project URL and publishable key (sb_publishable_...), both available in the dashboard's Connect dialog. Outside a tutorial, pass them in per environment with the dart-define-from-file flag instead of hardcoding them:

import 'package:flutter/material.dart';
import 'package:supabase_flutter/supabase_flutter.dart';

Future<void> main() async {
  WidgetsFlutterBinding.ensureInitialized();
  await Supabase.initialize(
    url: 'YOUR_SUPABASE_URL',
    publishableKey: 'YOUR_SUPABASE_PUBLISHABLE_KEY',
  );
  runApp(const MyApp());
}

final supabase = Supabase.instance.client;

Querying Data from Flutter

Query a table with from().select() and render the result with a FutureBuilder, one of Flutter's core widgets for async data:

class HomePage extends StatefulWidget {
  const HomePage({super.key});

  @override
  State<HomePage> createState() => _HomePageState();
}

class _HomePageState extends State<HomePage> {
  final _future = supabase.from('colors').select();

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: FutureBuilder<List<Map<String, dynamic>>>(
        future: _future,
        builder: (context, snapshot) {
          if (snapshot.hasError) {
            return Center(child: Text('Error: ${snapshot.error}'));
          }
          if (!snapshot.hasData) {
            return const Center(child: CircularProgressIndicator());
          }
          final colors = snapshot.data!;
          return ListView.builder(
            itemCount: colors.length,
            itemBuilder: (context, index) =>
                ListTile(title: Text(colors[index]['name'] as String)),
          );
        },
      ),
    );
  }
}

How Does Supabase Authentication Work in Flutter?

Supabase Auth issues a session (access and refresh tokens) that supabase_flutter stores and refreshes automatically. Every database request then carries the user's identity, which your RLS policies check.

Email and password sign-in is a single call:

Future<void> signInWithEmail(String email, String password) async {
  await supabase.auth.signInWithPassword(email: email, password: password);
}

For third-party providers, signInWithOAuth() opens the provider's login page in the browser and returns to your app through the redirect URL:

Future<void> signInWithGitHub() async {
  await supabase.auth.signInWithOAuth(
    OAuthProvider.github,
    redirectTo: 'io.supabase.flutter://login-callback/',
  );
}

For Google and Apple on mobile, prefer native sign-in: get an ID token from the platform SDK and pass it to signInWithIdToken(). Users stay in the app instead of bouncing through a browser. Supabase's Google and Apple guides cover the provider setup.

How Do You Stream Real-Time Supabase Data into Flutter?

Add the table to the supabase_realtime publication (Database > Publications in the dashboard, see the Realtime docs), then use stream() with a StreamBuilder. The stream emits the full, current result set on every change, and it respects RLS:

final _stream = supabase.from('colors').stream(primaryKey: ['id']);

@override
Widget build(BuildContext context) {
  return StreamBuilder<List<Map<String, dynamic>>>(
    stream: _stream,
    builder: (context, snapshot) {
      // Build your UI from snapshot.data
      return const SizedBox.shrink();
    },
  );
}

When you need to react to individual inserts, updates or deletes rather than redraw a list, subscribe to a channel instead:

supabase
    .channel('colors_changes')
    .onPostgresChanges(
      event: PostgresChangeEvent.all,
      schema: 'public',
      table: 'colors',
      callback: (payload) {
        final newRecord = payload.newRecord;
        final oldRecord = payload.oldRecord;
        // React to the change
      },
    )
    .subscribe();

Remove the channel with supabase.removeChannel() when the widget is disposed, or open connections pile up.

How Do You Upload Files to Supabase Storage from Flutter?

Create a bucket under Storage in the dashboard, then upload a File (for example, one returned by image_picker) to a path inside it:

Future<void> uploadAvatar(File file) async {
  final userId = supabase.auth.currentUser!.id;
  await supabase.storage
      .from('avatars')
      .upload('$userId/avatar.png', file, fileOptions: const FileOptions(upsert: true));
}

Storage access is governed by RLS policies on the storage.objects table, just like your own tables. Prefixing paths with the user's ID, as above, makes "users can only write their own folder" a one-line policy. The Storage access control guide has examples.

When Should a Flutter App Use Supabase Edge Functions?

Use Edge Functions for any logic that needs a secret: calling a payment provider, sending email, or writing data with elevated privileges. They are server-side TypeScript functions running on Deno, and you call them from Flutter with supabase.functions.invoke('function-name'). If a feature would otherwise require putting a secret key in the app, it belongs in an Edge Function.

How Do You Store Supabase Sessions Securely in Flutter?

By default, supabase_flutter v2 persists the session in SharedPreferences, which isn't encrypted and falls short of most mobile app security requirements. For apps handling sensitive data, store it in the platform keychain or keystore with flutter_secure_storage by subclassing LocalStorage and passing it through authOptions:

class SecureLocalStorage extends LocalStorage {
  final _storage = const FlutterSecureStorage();

  @override
  Future<void> initialize() async {}

  @override
  Future<String?> accessToken() =>
      _storage.read(key: supabasePersistSessionKey);

  @override
  Future<bool> hasAccessToken() =>
      _storage.containsKey(key: supabasePersistSessionKey);

  @override
  Future<void> persistSession(String persistSessionString) =>
      _storage.write(key: supabasePersistSessionKey, value: persistSessionString);

  @override
  Future<void> removePersistedSession() =>
      _storage.delete(key: supabasePersistSessionKey);
}

await Supabase.initialize(
  url: 'YOUR_SUPABASE_URL',
  publishableKey: 'YOUR_SUPABASE_PUBLISHABLE_KEY',
  authOptions: FlutterAuthClientOptions(localStorage: SecureLocalStorage()),
);

To disable session persistence entirely, pass EmptyLocalStorage() the same way.

Magic links, email confirmations and OAuth redirects all return to your app through a custom URL scheme, so each platform must register it. supabase_flutter v2 detects the auth callback automatically; you only handle platform configuration.

Android

Add an intent filter to the main activity in android/app/src/main/AndroidManifest.xml:

<intent-filter>
  <action android:name="android.intent.action.VIEW" />
  <category android:name="android.intent.category.DEFAULT" />
  <category android:name="android.intent.category.BROWSABLE" />
  <data android:scheme="YOUR_SCHEME" android:host="YOUR_HOST" />
</intent-filter>

See the Android deep linking docs for details.

iOS and macOS

Register the scheme in ios/Runner/Info.plist (and macos/Runner/Info.plist for macOS):

<key>CFBundleURLTypes</key>
<array>
  <dict>
    <key>CFBundleTypeRole</key>
    <string>Editor</string>
    <key>CFBundleURLSchemes</key>
    <array>
      <string>YOUR_SCHEME</string>
    </array>
  </dict>
</array>

Apple's guide to defining a custom URL scheme covers the options.

Windows

Windows needs changes to the runner code and a registry entry for the scheme. Follow the app_links Windows setup, and register the scheme from the app with url_protocol or from your installer.

What Must Be in Place Before a Supabase Flutter App Goes to Production?

Run through these checks before launch, ideally as part of security by design from the first sprint:

  • Enable RLS on every exposed table and test the policies. The dashboard's Security Advisor flags tables without RLS. Test policies as a signed-in user, not as an admin in the SQL Editor (which bypasses them).

  • Keep the secret key out of the app. The secret key (sb_secret_..., or the legacy service_role key) bypasses RLS entirely and can be extracted from a compiled binary. Use it only in Edge Functions or your own server.

  • Switch to the new API keys. Supabase is retiring the legacy anon and service_role keys by the end of 2026. Ship builds that use the publishable key before you disable the legacy ones, because users on old app versions will lose access the moment you do.

  • Keep schema and policies in version-controlled migrations with the Supabase CLI, and use separate projects for staging and production instead of editing production in the dashboard.

  • Test deep links on every platform. A wrong scheme breaks magic links and OAuth silently on one platform while working on the others.

  • Confirm compliance up front. The hosted platform supports HIPAA workloads under a Business Associate Agreement on paid plans, and data lives in the region you pick at project creation, which can't be changed later without migrating. If your requirements go beyond that, get a compliance and security review before choosing between hosted and self-hosted.

Key Takeaways

  • Supabase lets a Flutter team ship auth, a Postgres database, storage and real-time features without building a backend.

  • Security lives in the database: RLS policies decide access, and tables created with SQL start with RLS off.

  • Never ship a secret or service_role key in a Flutter app; put privileged logic in Edge Functions.

  • Migrate to publishable keys before the legacy keys retire at the end of 2026.

  • Supabase saves the most time when backend capacity, not backend complexity, is the bottleneck.

When Does Supabase Make Sense for a Flutter App?

Supabase fits teams whose constraint is backend capacity, not backend complexity. If the app needs auth, a relational database, storage and real-time updates, the platform covers all four and a Flutter developer can own the whole feature. Apps built around heavy custom server logic, unusual data models or strict infrastructure requirements will spend more time working around the platform than the setup saves.

At Monterail, we build cross-platform mobile apps in Flutter and help teams decide where their backend should live. If you're weighing Supabase against a custom backend, or preparing a Supabase app for production, talk to our team.

Supabase and Flutter FAQ

Marcin Wróblewski  avatar
Marcin Wróblewski
Senior Mobile Developer
Linkedin
Open-source enthusiast, focusing on Dart and Flutter due to their technical excellence. Codeusse’s creator.