supabase_flutter
Flutter client library for Supabase.
Guides · Reference Docs · Migration Guides
Getting Started
Import the package:
import 'package:supabase_flutter/supabase_flutter.dart';
Initialize Supabase before using it:
import 'package:supabase_flutter/supabase_flutter.dart';
void main() async {
WidgetsFlutterBinding.ensureInitialized();
await Supabase.initialize(
url: SUPABASE_URL,
publishableKey: SUPABASE_KEY,
);
runApp(MyApp());
}
// It's handy to then extract the Supabase client in a variable for later uses
final supabase = Supabase.instance.client;
Usage example
Authentication
final supabase = Supabase.instance.client;
// Email and password sign up
await supabase.auth.signUp(
email: email,
password: password,
);
// Email and password login
await supabase.auth.signInWithPassword(
email: email,
password: password,
);
// Magic link login
await supabase.auth.signInWithOtp(email: 'my_email@example.com');
// Listen to auth state changes
supabase.auth.onAuthStateChange.listen((data) {
final AuthChangeEvent event = data.event;
final Session? session = data.session;
// Do something when there is an auth event
});
Native Apple Sign in
You can perform Apple sign in using the sign_in_with_apple package on Flutter.
Follow the instructions on README of the sign_in_with_apple package to setup the native Apple sign in on iOS and macOS.
Once the setup is complete on the Flutter app, add the bundle ID of your app to your Supabase dashboard in Authentication -> Providers -> Apple in order to register your app with Supabase.
import 'package:sign_in_with_apple/sign_in_with_apple.dart';
import 'package:supabase_flutter/supabase_flutter.dart';
/// Performs Apple sign in on iOS or macOS
Future<AuthResponse> signInWithApple() async {
final rawNonce = supabase.auth.generateRawNonce();
final hashedNonce = sha256.convert(utf8.encode(rawNonce)).toString();
final credential = await SignInWithApple.getAppleIDCredential(
scopes: [
AppleIDAuthorizationScopes.email,
AppleIDAuthorizationScopes.fullName,
],
nonce: hashedNonce,
);
final idToken = credential.identityToken;
if (idToken == null) {
throw const AuthException(
'Could not find ID Token from generated credential.');
}
return signInWithIdToken(
provider: OAuthProvider.apple,
idToken: idToken,
nonce: rawNonce,
);
}
Native Google sign in
You can perform native Google sign in on Android and iOS using google_sign_in. For platform specific settings, follow the instructions on README of the package.
First, create client IDs for your app. You need to create a web client ID as well to perform Google sign-in with Supabase.
Once you have registered your app and created the client IDs, add the web client ID in your Supabase dashboard in Authentication -> Providers -> Google. Also turn on the Skip nonce check option, which will enable Google sign-in on iOS.
At this point you can perform native Google sign in using the following code. Be sure to replace the webClientId and iosClientId with your own.
The following example is very basic. Please refer to the google_sign_in package for the correct details.
import 'package:google_sign_in/google_sign_in.dart';
import 'package:supabase_flutter/supabase_flutter.dart';
...
Future<AuthResponse> _googleSignIn() async {
/// TODO: update the Web client ID with your own.
///
/// Web Client ID that you registered with Google Cloud.
const webClientId = 'my-web.apps.googleusercontent.com';
/// TODO: update the iOS client ID with your own.
///
/// iOS Client ID that you registered with Google Cloud.
const iosClientId = 'my-ios.apps.googleusercontent.com';
// Google sign in on Android will work without providing the Android
// Client ID registered on Google Cloud.
final GoogleSignIn signIn = GoogleSignIn.instance;
// At the start of your app, initialize the GoogleSignIn instance
unawaited(
signIn.initialize(clientId: iosClientId, serverClientId: webClientId));
// Perform the sign in
final googleAccount = await signIn.authenticate();
final googleAuthorization = await googleAccount.authorizationClient.authorizationForScopes([]);
final googleAuthentication = googleAccount!.authentication;
final idToken = googleAuthentication.idToken;
final accessToken = googleAuthorization.accessToken;
if (idToken == null) {
throw 'No ID Token found.';
}
return supabase.auth.signInWithIdToken(
provider: OAuthProvider.google,
idToken: idToken,
accessToken: accessToken,
);
}
...
Native Facebook sign in
You can use flutter_facebook_auth ^7.1.7 with signInWithIdToken on both iOS and Android.
- iOS uses Facebook's Limited Login (
LoginTracking.limited), which returns aLimitedTokencontaining an OIDC ID token directly. - Android requires
LoginBehavior.webOnlyand theopenidpermission to receive an OIDC ID token inClassicToken.authenticationToken.
import 'dart:io';
import 'package:flutter_facebook_auth/flutter_facebook_auth.dart';
import 'package:supabase_flutter/supabase_flutter.dart';
Future<AuthResponse> _facebookSignIn() async {
late final LoginResult result;
if (Platform.isIOS) {
result = await FacebookAuth.instance.login(
permissions: ['public_profile', 'email'],
loginTracking: LoginTracking.limited,
);
} else {
// Android: webOnly behavior with openid scope returns an OIDC ID token.
result = await FacebookAuth.instance.login(
permissions: ['public_profile', 'email', 'openid'],
loginBehavior: LoginBehavior.webOnly,
);
}
if (result.status != LoginStatus.success) {
throw 'Facebook sign in failed: ${result.message}';
}
final accessToken = result.accessToken;
final String idToken;
if (accessToken is LimitedToken) {
idToken = accessToken.tokenString;
} else if (accessToken is ClassicToken) {
final authToken = accessToken.authenticationToken;
if (authToken == null) {
throw 'No ID token returned. Make sure to use LoginBehavior.webOnly and include the openid scope on Android.';
}
idToken = authToken;
} else {
throw 'Unexpected Facebook token type: ${accessToken?.runtimeType}';
}
return Supabase.instance.client.auth.signInWithIdToken(
provider: OAuthProvider.facebook,
idToken: idToken,
);
}
Alternatively, if you do not want to use the native Facebook SDK, you can use the web-based signInWithOAuth() method. This will open the device's web browser to perform the classic Facebook OAuth 2.0 login flow.
import 'package:supabase_flutter/supabase_flutter.dart';
Future<void> _facebookSignInWeb() async {
await Supabase.instance.client.auth.signInWithOAuth(
OAuthProvider.facebook,
redirectTo: 'io.supabase.flutterdemo://login-callback',
);
}
OAuth login
The signInWithIdToken() method supports providers like Apple, Google, Facebook, Kakao, and Keycloak. For other providers, you need to use the signInWithOAuth() method to perform OAuth login. This will open the web browser to perform the OAuth login.
Use the redirectTo parameter to redirect the user to a deep link to bring the user back to the app. Learn more about setting up deep links in Deep link config.
// Perform web based OAuth login
await supabase.auth.signInWithOAuth(
OAuthProvider.github,
redirectTo: kIsWeb ? null : 'io.supabase.flutter://callback',
);
// Listen to auth state changes in order to detect when the OAuth login is complete.
supabase.auth.onAuthStateChange.listen((data) {
final AuthChangeEvent event = data.event;
if(event == AuthChangeEvent.signedIn) {
// Do something when user sign in
}
});
Passkeys
Passkeys are a BETA feature. Enable them for your project in the Supabase Dashboard under Authentication > Configuration > Passkeys before using these methods.
supabase_flutter performs the server side of the WebAuthn ceremony for you and delegates the platform prompt (FaceID/TouchID/security key) to an authenticator you supply. This keeps supabase_flutter free of a passkey plugin dependency, so apps that do not use passkeys are not forced to raise their minimum platform versions.
Add a passkey plugin to your own app and pass its authenticator in. The passkeys plugin's PasskeyAuthenticator implements the PasskeyAuthenticatorInterface these methods expect (since passkeys 2.21.0), but you can pass any implementation of that interface.
import 'package:passkeys/authenticator.dart';
final authenticator = PasskeyAuthenticator();
// Sign in with a passkey. The user is prompted to pick and unlock a passkey.
await supabase.auth.signInWithPasskey(authenticator);
// Register a new passkey for the signed in user.
await supabase.auth.registerPasskey(authenticator);
// Manage the signed in user's passkeys with the server side API.
final passkeys = await supabase.auth.passkey.list();
await supabase.auth.passkey.update(passkeyId: passkeys.first.id, friendlyName: 'My phone');
await supabase.auth.passkey.delete(passkeyId: passkeys.first.id);
The platform ceremony is handled by whichever plugin you add. Refer to your plugin's documentation, for example the passkeys package documentation, for its platform requirements, setup, and how to handle ceremony failures such as the user cancelling.
Database
Database methods are used to perform basic CRUD operations using the Supabase REST API. Full list of supported operators can be found here.
// Select data with filters
final data = await supabase
.from('cities')
.select()
.eq('country_id', 1) // equals filter
.neq('name', 'The shire'); // does not equal filter
// Insert a new row
await supabase
.from('cities')
.insert({'name': 'The Shire', 'country_id': 554});
Realtime
Realtime data as Stream
To receive realtime updates, you have to first enable Realtime on from your Supabase console. You can read more here on how to enable it.
Warning When using
stream()with aStreamBuilder, make sure to persist the stream value as a variable in aStatefulWidgetinstead of directly constructing the stream within your widget tree, which could cause rapid rebuilds that will lead to losing realtime connection.
class MyWidget extends StatefulWidget {
const MyWidget({Key? key}) : super(key: key);
@override
State<MyWidget> createState() => _MyWidgetState();
}
class _MyWidgetState extends State<MyWidget> {
// Persisting the future as local variable to prevent refetching upon rebuilds.
final stream = supabase.from('countries').stream(primaryKey: ['id']);
@override
Widget build(BuildContext context) {
return StreamBuilder<List<Map<String, dynamic>>>(
stream: stream,
builder: (context, snapshot) {
// return your widget with the data from snapshot
},
);
}
}
Postgres Changes
You can get notified whenever there is a change in your Supabase tables.
final myChannel = supabase.channel('my_channel');
myChannel
.onPostgresChanges(
event: PostgresChangeEvent.all,
schema: 'public',
table: 'countries',
)
.listen((payload) {
// Do something fun or interesting when there is a change on the database
});
myChannel.subscribe();
Broadcast
Broadcast lets you send and receive low latency messages between connected clients by bypassing the database.
final myChannel = supabase.channel('my_channel');
// Listen to `cursor-pos` broadcast events
myChannel.onBroadcast(event: 'cursor-pos').listen((payload) {
// Do something fun or interesting with the received message
});
myChannel.subscribe();
// Send a broadcast message to other connected clients
await myChannel.sendBroadcastMessage(
event: 'cursor-pos',
payload: {'x': 30, 'y': 50},
);
Presence
Presence let's you easily create "I'm online" feature.
final myChannel = supabase.channel('my_channel');
// Listen to presence events
myChannel.onPresenceSync.listen((payload) {
final onlineUsers = myChannel.presenceState();
// handle sync event
});
myChannel.onPresenceJoin.listen((payload) {
// New users have joined
});
myChannel.onPresenceLeave.listen((payload) {
// Users have left
});
myChannel.onStatusChange.listen((change) async {
if (change.status == RealtimeSubscribeStatus.subscribed) {
// Send the current user's state upon subscribing
await myChannel.track({'online_at': DateTime.now().toIso8601String()});
}
});
myChannel.subscribe();
Storage
final file = File('example.txt');
file.writeAsStringSync('File content');
await supabase.storage
.from('my_bucket')
.upload('my/path/to/files/example.txt', file);
// Use the `uploadBinary` method to upload files on Flutter web
await supabase.storage
.from('my_bucket')
.uploadBinary('my/path/to/files/example.txt', file.readAsBytesSync());
Edge Functions
final data = await supabase.functions.invoke('get_countries');
Deep links
Why do you need to setup deep links
You need to setup deep links if you want your native app to open when a user clicks on a link. User clicking on a link and the app opens up happens in a few scenarios when you use Supabase auth, and in order to support those scenarios, you need to setup deep links.
When do you need to setup deep links
- Magic link login
- Have
confirm emailenabled and are using email login - Resetting password for email login
- Calling
.signInWithOAuth()method
*Currently supabase_flutter supports deep links on Android, iOS, Web, MacOS and Windows.
Dashboard Deep link config
- Go to your Supabase project Authentication Settings page.
- You need to enter your app redirect callback on
Additional Redirect URLsfield.
The redirect callback url should have this format [YOUR_SCHEME]://[YOUR_HOSTNAME]. Here, io.supabase.flutterdemo://login-callback is just an example, you can choose whatever you would like for YOUR_SCHEME and YOUR_HOSTNAME as long as the scheme is unique across the user's device. For this reason, typically a reverse domain of your website is used.

Flutter Deep link config
supabase_flutter uses app_link internally to handle deep links. You can find the platform specific config to setup deep links in the following.
https://github.com/llfbandit/app_links/tree/master?tab=readme-ov-file#getting-started
Platform specific config
Follow the guide to find additional platform specific configs for your OAuth provider.
https://supabase.io/docs/guides/auth#third-party-logins
Custom LocalStorage
By default, supabase_flutter uses the SharedPreferencesAsync API of shared_preferences to persist the user session. If your own code still uses the legacy SharedPreferences API, migrate it to SharedPreferencesAsync: on Windows and Linux both APIs rewrite the same file from their own cache, so a write through one drops what the other wrote, and a mixed setup can lose your preferences as well as the session.
However, you can use any other methods by creating a LocalStorage implementation. For example, we can use flutter_secure_storage plugin to store the user session in a secure storage.
The key the session is stored under is derived from your project URL by Supabase.initialize. You only pass it yourself when you construct a LocalStorage, as below, and defaultPersistSessionKey gives you the same key the default storage uses.
// Define the custom LocalStorage implementation
class MySecureStorage extends LocalStorage {
MySecureStorage({required this.persistSessionKey});
final String persistSessionKey;
final storage = FlutterSecureStorage();
@override
Future<void> initialize() async {}
@override
Future<String?> accessToken() async {
return storage.read(key: persistSessionKey);
}
@override
Future<bool> hasAccessToken() async {
return storage.containsKey(key: persistSessionKey);
}
@override
Future<void> persistSession(String persistSessionString) async {
return storage.write(key: persistSessionKey, value: persistSessionString);
}
@override
Future<void> removePersistedSession() async {
return storage.delete(key: persistSessionKey);
}
}
// use it when initializing
Supabase.initialize(
...
authOptions: FlutterAuthClientOptions(
localStorage: MySecureStorage(
persistSessionKey: defaultPersistSessionKey(supabaseUrl),
),
),
);
You can also use EmptyLocalStorage to disable session persistence:
Supabase.initialize(
// ...
authOptions: FlutterAuthClientOptions(
localStorage: const EmptyLocalStorage(),
),
);
Logging
All Supabase packages emit their logs through the logging package, using loggers under the shared supabase hierarchy. The packages only emit records; they never print anything and never change any package:logging settings. Your application decides whether, where, and at which level logs are handled.
Listen to Supabase logs
The simplest setup listens on the root logger and filters on the logger name. It receives the records allowed by Logger.root.level, which defaults to Level.INFO; set Logger.root.level = Level.ALL to also receive the fine-grained records, including the Level.FINEST payloads described below.
import 'package:flutter/foundation.dart';
import 'package:logging/logging.dart';
void main() {
Logger.root.onRecord.listen((record) {
if (record.loggerName.startsWith('supabase.')) {
debugPrint('${record.loggerName}: ${record.level.name}: '
'${record.message} ${record.error ?? ''}');
}
});
// ...
}
Filter Supabase logs by level
To control the level of Supabase logs independently of the rest of your application, enable hierarchical logging and configure the supabase logger directly:
import 'package:flutter/foundation.dart';
import 'package:logging/logging.dart';
void main() {
hierarchicalLoggingEnabled = true;
Logger('supabase')
..level = Level.INFO // or Level.ALL to also receive request payloads
..onRecord.listen((record) {
debugPrint('${record.loggerName}: ${record.level.name}: '
'${record.message} ${record.error ?? ''}');
});
// ...
}
Without hierarchicalLoggingEnabled = true, package:logging only supports listening and setting levels on Logger.root.
Log levels
Level.CONFIG: client configuration during initialization.Level.FINEST: wire-level details such as request and response payloads. These records can contain sensitive data like row data, so handle them accordingly. Credentials never appear in any record at any level: headers, URL parameters, and payload fields that carry an API key, access token, or other credential are replaced by<redacted>before they are logged.Level.FINE: internal lifecycle events, for example the realtime socket connecting or disconnecting.Level.INFO: notable events, for example completed initialization or session recovery.Level.WARNING: recoverable problems, for example a failed retry attempt or an unexpected disconnect.
Package loggers
supabase_flutter:Logger('supabase.flutter')supabase:Logger('supabase.dart')postgrest:Logger('supabase.postgrest')supabase_auth:Logger('supabase.auth')supabase_realtime:Logger('supabase.realtime')supabase_storage:Logger('supabase.storage')supabase_functions:Logger('supabase.functions')iceberg:Logger('supabase.storage.iceberg')
Migrating Guide
The breaking changes of each major version, and what to do about them, are documented in MIGRATION.md.
Contributing
- Fork the repo on GitHub
- Clone the project to your own machine
- Commit changes to your own branch
- Push your work back up to your fork
- Submit a Pull request so that we can review your changes and merge
License
This repo is licensed under MIT.
Resources
Libraries
- supabase_flutter
- Flutter integration for Supabase.