Home

Flutter Client Library

supabase-flutter

This reference documents every object and method available in Supabase's Flutter library, supabase-flutter. You can use supabase-flutter to interact with your Postgres database, listen to database changes, invoke Deno Edge Functions, build login and user management functionality, and manage large files.

We also provide a supabase package for non-Flutter projects.

Installing

Install from pub.dev#

You can install Supabase package from pub.dev

flutter pub add supabase_flutter

Initializing

You can initialize Supabase with the static initialize() method of Supabase class.

The Supabase client is your entrypoint to the rest of the Supabase functionality and is the easiest way to interact with everything we offer within the Supabase ecosystem.


Future<void> main() async {
  await Supabase.initialize(
    url: 'https://xyzcompany.supabase.co',
    anonKey: 'public-anon-key',
  );

  runApp(MyApp());
}

// Get a reference your Supabase client
final supabase = Supabase.instance.client;

Fetch data

Performs vertical filtering with SELECT.

  • By default, Supabase projects will return a maximum of 1,000 rows. This setting can be changed in Project API Settings. It's recommended that you keep it low to limit the payload size of accidental or malicious requests. You can use range() queries to paginate through your data.
  • select() can be combined with Modifiers
  • select() can be combined with Filters
  • If using the Supabase hosted platform apikey is technically a reserved keyword, since the API gateway will pluck it out for authentication. It should be avoided as a column name.

final data = await supabase
  .from('cities')
  .select('name');

Insert data

Performs an INSERT into the table.


await supabase
    .from('cities')
    .insert({'name': 'The Shire', 'country_id': 554});

Update data

Performs an UPDATE on the table.

  • update() should always be combined with Filters to target the item(s) you wish to update.

await supabase
  .from('cities')
  .update({ 'name': 'Middle Earth' })
  .match({ 'name': 'Auckland' });

Upsert data

Performs an UPSERT into the table.

  • Primary keys should be included in the data payload in order for an update to work correctly.
  • Primary keys must be natural, not surrogate. There are however, workarounds for surrogate primary keys.

await supabase
  .from('messages')
  .upsert({ 'id': 3, 'message': 'foo', 'username': 'supabot' });

Delete data

Performs a DELETE on the table.

  • delete() should always be combined with Filters to target the item(s) you wish to delete.

await supabase
  .from('cities')
  .delete()
  .match({ 'id': 666 });

Call a Postgres function

You can call stored procedures as a "Remote Procedure Call".

That's a fancy way of saying that you can put some logic into your database then call it from anywhere. It's especially useful when the logic rarely changes - like password resets and updates.


final data = await supabase
  .rpc('hello_world');

Using filters

Filters allow you to only return rows that match certain conditions.

Filters can be used on select(), update(), and delete() queries.

If a Database function returns a table response, you can also apply filters.


final data = await supabase
  .from('cities')
  .select('name, country_id')
  .eq('name', 'The Shire');  // Correct

final data = await supabase
  .from('cities')
  .eq('name', 'The Shire')  // Incorrect
  .select('name, country_id');

Column is equal to a value

Finds all rows whose value on the stated column exactly matches the specified value.


final data = await supabase
  .from('cities')
  .select('name, country_id')
  .eq('name', 'The shire');

Column is not equal to a value

Finds all rows whose value on the stated column doesn't match the specified value.


final data = await supabase
  .from('cities')
  .select('name, country_id')
  .neq('name', 'The shire');

Column is greater than a value

Finds all rows whose value on the stated column is greater than the specified value.


final data = await supabase
  .from('cities')
  .select('name, country_id')
  .gt('country_id', 250);

Column is greater than or equal to a value

Finds all rows whose value on the stated column is greater than or equal to the specified value.


final data = await supabase
  .from('cities')
  .select('name, country_id')
  .gte('country_id', 250);

Column is less than a value

Finds all rows whose value on the stated column is less than the specified value.


final data = await supabase
  .from('cities')
  .select('name, country_id')
  .lt('country_id', 250);

Column is less than or equal to a value

Finds all rows whose value on the stated column is less than or equal to the specified value.


final data = await supabase
  .from('cities')
  .select('name, country_id')
  .lte('country_id', 250);

Column matches a pattern

Finds all rows whose value in the stated column matches the supplied pattern (case sensitive).


final data = await supabase
  .from('cities')
  .select('name, country_id')
  .like('name', '%la%');

Column matches a case-insensitive pattern

Finds all rows whose value in the stated column matches the supplied pattern (case insensitive).


final data = await supabase
  .from('cities')
  .select('name, country_id')
  .ilike('name', '%la%');

Column is a value

A check for exact equality (null, true, false), finds all rows whose value on the stated column exactly match the specified value.

is_ and in_ filter methods are suffixed with _ to avoid collisions with reserved keywords.


final data = await supabase
  .from('cities')
  .select('name, country_id')
  .is_('name', null);

Column is in an array

Finds all rows whose value on the stated column is found on the specified values.

is_ and in_ filter methods are suffixed with _ to avoid collisions with reserved keywords.


final data = await supabase
  .from('cities')
  .select('name, country_id')
  .in_('name', ['Rio de Janeiro', 'San Francisco']);

Column contains every element in a value


final data = await supabase
  .from('countries')
  .select('name, id, main_expor')
  .contains('main_exports', ['oil']);

Contained by value


final data = await supabase
  .from('countries')
  .select('name, id, main_expor')
  .containedBy('main_exports', ['cars', 'food', 'machine']);

Greater than a range


final data = await supabase
  .from('countries')
  .select('name, id, population_range_millions')
  .rangeGt('population_range_millions', '[150, 250]');

Greater than or equal to a range


final data = await supabase
  .from('countries')
  .select('name, id, population_range_millions')
  .rangeGte('population_range_millions', '[150, 250]');

Less than a range


final data = await supabase
  .from('countries')
  .select('name, id, population_range_millions')
  .rangeLt('population_range_millions', '[150, 250]');

Less than or equal to a range


final data = await supabase
  .from('countries')
  .select('name, id, population_range_millions')
  .rangeLte('population_range_millions', '[150, 250]');

Mutually exclusive to a range


final data = await supabase
  .from('countries')
  .select('name, id, population_range_millions')
  .rangeAdjacent('population_range_millions', '[70, 185]');

With a common element


final data = await supabase
  .from('countries')
  .select('name, id, main_expor')
  .overlaps('main_exports', ['computers', 'minerals']);

Match a string

Finds all rows whose tsvector value on the stated column matches to_tsquery(query).

Match an associated value

Finds all rows whose columns match the specified query object.


final data = await supabase
  .from('cities')
  .select('name, country_id')
  .match({'name': 'Beijing', 'country_id': 156});

Don't match the filter

Finds all rows which doesn't satisfy the filter.

  • .not() expects you to use the raw PostgREST syntax for the filter names and values.

    .not('name','eq','Paris')
    .not('arraycol','cs','{"a","b"}') // Use Postgres array {} for array column and 'cs' for contains.
    .not('rangecol','cs','(1,2]') // Use Postgres range syntax for range column.
    .not('id','in','(6,7)')  // Use Postgres list () and 'in' for in_ filter.
    .not('id','in','(${mylist.join(',')})')  // You can insert a Dart list array.
    

final data = await supabase
  .from('cities')
  .select('name, country_id')
  .not('name', 'eq', 'Paris');

Match at least one filter

Finds all rows satisfying at least one of the filters.

  • .or() expects you to use the raw PostgREST syntax for the filter names and values.

    .or('id.in.(6,7),arraycol.cs.{"a","b"}')  // Use Postgres list () and 'in' for in_ filter. Array {} and 'cs' for contains.
    .or('id.in.(${mylist.join(',')}),arraycol.cs.{${mylistArray.join(',')}}')	// You can insert a Dart list for list or array column.
    .or('id.in.(${mylist.join(',')}),rangecol.cs.(${mylistRange.join(',')}]')	// You can insert a Dart list for list or range column.
    

final data = await supabase
  .from('cities')
  .select('name, country_id')
  .or('id.eq.20,id.eq.30');

Match the filter

Finds all rows whose column satisfies the filter.

  • .filter() expects you to use the raw PostgREST syntax for the filter names and values, so it should only be used as an escape hatch in case other filters don't work.
      .filter('arraycol','cs','{"a","b"}') // Use Postgres array {} and 'cs' for contains.
      .filter('rangecol','cs','(1,2]') // Use Postgres range syntax for range column.
      .filter('id','in','(6,7)')  // Use Postgres list () and 'in' for in_ filter.
      .filter('id','cs','{${mylist.join(',')}}')  // You can insert a Dart array list.
    

final data = await supabase
  .from('cities')
  .select('name, country_id')
  .filter('name', 'in', '("Paris","Tokyo")');

Using Modifiers

Filters work on the row level—they allow you to return rows that only match certain conditions without changing the shape of the rows. Modifiers are everything that don't fit that definition—allowing you to change the format of the response (e.g., returning a CSV string).

Modifiers must be specified after filters. Some modifiers only apply for queries that return rows (e.g., select() or rpc() on a function that returns a table response).

Order the query

Orders the result with the specified column.


final data = await supabase
  .from('cities')
  .select('name, country_id')
  .order('id',  { ascending: false });

Limit the query

Limits the result with the specified count.


final data = await supabase
  .from('cities')
  .select('name, country_id')
  .limit(1);

Limit the query to a range

Limits the result to rows within the specified range, inclusive.


final data = await supabase
  .from('cities')
  .select('name, country_id')
  .range(0,3);

Retrieve the query as one row

Retrieves only one row from the result. Result must be one row (e.g. using limit), otherwise this will result in an error.


final data = await supabase
  .from('cities')
  .select('name, country_id')
  .single();

Create a new user

Creates a new user.

  • By default, the user needs to verify their email address before logging in. To turn this off, disable Confirm email in your project.
  • Confirm email determines if users need to confirm their email address after signing up.
    • If Confirm email is enabled, a user is returned but session is null.
    • If Confirm email is disabled, both a user and a session are returned.
  • When the user confirms their email address, they are redirected to the SITE_URL by default. You can modify your SITE_URL or add additional redirect URLs in your project.
  • If signUp() is called for an existing confirmed user:
    • If Confirm email is enabled in your project, an obfuscated/fake user object is returned.
    • If Confirm email is disabled, the error message, User already registered is returned.

final AuthResponse res = await supabase.auth.signUp(
  email: 'example@email.com',
  password: 'example-password',
);
final Session? session = res.session;
final User? user = res.user;

Sign in a user

Log in an existing user using email or phone number with password.

  • Requires either an email and password or a phone number and password.

final AuthResponse res = await supabase.auth.signInWithPassword(
  email: 'example@email.com',
  password: 'example-password',
);
final Session? session = res.session;
final User? user = res.user;

Sign in a user through OTP

  • Requires either an email or phone number.
  • This method is used for passwordless sign-ins where a OTP is sent to the user's email or phone number.
  • If you're using an email, you can configure whether you want the user to receive a magiclink or a OTP.
  • If you're using phone, you can configure whether you want the user to receive a OTP.
  • The magic link's destination URL is determined by the SITE_URL. You can modify the SITE_URL or add additional redirect urls in your project.

await supabase.auth.signInWithOtp(
  email: 'example@email.com',
  emailRedirectTo: kIsWeb ? null : 'io.supabase.flutter://signin-callback/',
);

Sign in a user through OAuth

Signs the user in using third party OAuth providers.

  • This method is used for signing in using a third-party provider.
  • Supabase supports many different third-party providers.

await supabase.auth.signInWithOAuth(Provider.github);

Sign out a user

Signs out the current user, if there is a logged in user.

  • In order to use the signOut() method, the user needs to be signed in first.

await supabase.auth.signOut();

Verify and log in through OTP

  • The verifyOtp method takes in different verification types. If a phone number is used, the type can either be sms or phone_change. If an email address is used, the type can be one of the following: signup, magiclink, recovery, invite or email_change.
  • The verification type used should be determined based on the corresponding auth method called before verifyOtp to sign up / sign-in a user.

final AuthResponse res = await supabase.auth.verifyOTP(
  type: OtpType.sms,
  token: '111111',
  phone: '+13334445555',
);
final Session? session = res.session;
final User? user = res.user;

Retrieve a session

Returns the session data, if there is an active session.


final Session? session = supabase.auth.currentSession;

Retrieve a user

Returns the user data, if there is a logged in user.


final User? user = supabase.auth.currentUser;

Update a user

Updates user data, if there is a logged in user.

  • In order to use the updateUser() method, the user needs to be signed in first.
  • By Default, email updates sends a confirmation link to both the user's current and new email. To only send a confirmation link to the user's new email, disable Secure email change in your project's email auth provider settings.

final UserResponse res = await supabase.auth.updateUser(
  UserAttributes(
    email: 'example@email.com',
  ),
);
final User? updatedUser = res.user;

Listen to auth events

Receive a notification every time an auth event happens.

  • Types of auth events: AuthChangeEvent.passwordRecovery, AuthChangeEvent.signedIn, AuthChangeEvent.signedOut, AuthChangeEvent.tokenRefreshed, AuthChangeEvent.userUpdatedand AuthChangeEvent.userDeleted

final authSubscription = supabase.auth.onAuthStateChange.listen((data) {
  final AuthChangeEvent event = data.event;
  final Session? session = data.session;
});

Invoke a function

Invokes a Supabase Function. See the guide for details on writing Functions.

  • Requires an Authorization header.
  • Invoke params generally match the Fetch API spec.

final res = await supabase.functions.invoke('hello', body: {'foo': 'baa'});
final data = res.data;

Listen to database changes

Notifies of data at the queried table.

  • stream() will emit the initial data as well as any further change on the database as Stream of List<Map<String, dynamic>> by combining Postgrest and Realtime.
  • Takes a list of primary key columns as its argument.

supabase.from('countries')
  .stream(primaryKey: ['id'])
  .listen((List<Map<String, dynamic>> data) {
  // Do something awesome with the data
});

Subscribe to channel

Subscribe to realtime changes in your database.

  • Realtime is disabled by default for new Projects for better database performance and security. You can turn it on by managing replication.
  • If you want to receive the "previous" data for updates and deletes, you will need to set REPLICA IDENTITY to FULL, like this: ALTER TABLE your_table REPLICA IDENTITY FULL;

supabase.channel('*').on(
  RealtimeListenTypes.postgresChanges,
  ChannelFilter(event: '*', schema: '*'),
  (payload, [ref]) {
    print('Change received: ${payload.toString()}');
  },
).subscribe();

Unsubscribe from a channel

Unsubscribes and removes Realtime channel from Realtime client.

  • Removing a channel is a great way to maintain the performance of your project's Realtime service as well as your database if you're listening to Postgres changes. Supabase will automatically handle cleanup 30 seconds after a client is disconnected, but unused channels may cause degradation as more clients are simultaneously subscribed.

final status = await supabase.removeChannel(channel);

Unsubscribe from all channels

Unsubscribes and removes all Realtime channels from Realtime client.

  • Removing channels is a great way to maintain the performance of your project's Realtime service as well as your database if you're listening to Postgres changes. Supabase will automatically handle cleanup 30 seconds after a client is disconnected, but unused channels may cause degradation as more clients are simultaneously subscribed.

final statuses = await supabase.removeAllChannels();

Retrieve all channels

Returns all Realtime channels.


final channels = supabase.getChannels();

Create a bucket

Creates a new Storage bucket

  • Policy permissions required:
    • buckets permissions: insert
    • objects permissions: none

final String bucketId = await supabase
  .storage
  .createBucket('avatars');

Retrieve a bucket

Retrieves the details of an existing Storage bucket.

  • Policy permissions required:
    • buckets permissions: select
    • objects permissions: none

final Bucket bucket = await supabase
  .storage
  .getBucket('avatars');

List all buckets

Retrieves the details of all Storage buckets within an existing product.

  • Policy permissions required:
    • buckets permissions: select
    • objects permissions: none

final List<Bucket> bucke = await supabase
  .storage
  .listBuckets();

Update a bucket

Updates a new Storage bucket

  • Policy permissions required:
    • buckets permissions: update
    • objects permissions: none

final res = await supabase
  .storage
  .updateBucket('avatars', const BucketOptions(public: false));

Delete a bucket

Deletes an existing bucket. A bucket can't be deleted with existing objects inside it. You must first empty() the bucket.

  • Policy permissions required:
    • buckets permissions: select and delete
    • objects permissions: none

final String result = await supabase
  .storage
  .deleteBucket('avatars');

Empty a bucket

Removes all objects inside a single bucket.

  • Policy permissions required:
    • buckets permissions: select
    • objects permissions: select and delete

final String result = await supabase
  .storage
  .emptyBucket('avatars');

Upload a file

Uploads a file to an existing bucket.

  • Policy permissions required:
    • buckets permissions: none
    • objects permissions: insert

final avatarFile = File('path/to/file');
final String path = await supabase.storage.from('avatars').upload(
      'public/avatar1.png',
      avatarFile,
      fileOptions: const FileOptions(cacheControl: '3600', upsert: false),
    );

Download a file

Downloads a file.

  • Policy permissions required:
    • buckets permissions: none
    • objects permissions: select

final Uint8List file = await supabase
  .storage
  .from('avatars')
  .download('avatar1.png');

List all files in a bucket

Lists all the files within a bucket.

  • Policy permissions required:
    • buckets permissions: none
    • objects permissions: select

final List<FileObject> objec = await supabase
  .storage
  .from('avatars')
  .list();

Replace an existing file

Replaces an existing file at the specified path with a new one.

  • Policy permissions required:
    • buckets permissions: none
    • objects permissions: update and select

final avatarFile = File('path/to/local/file');
final String path = await supabase.storage.from('avatars').update(
      'public/avatar1.png',
      avatarFile,
      fileOptions: const FileOptions(cacheControl: '3600', upsert: false),
    );

Move an existing file

Moves an existing file, optionally renaming it at the same time.

  • Policy permissions required:
    • buckets permissions: none
    • objects permissions: update and select

final String result = await supabase
  .storage
  .from('avatars')
  .move('public/avatar1.png', 'private/avatar2.png');

Delete files in a bucket

Deletes files within the same bucket

  • Policy permissions required:
    • buckets permissions: none
    • objects permissions: delete and select

final List<FileObject> objec = await supabase
  .storage
  .from('avatars')
  .remove(['avatar1.png']);

Create a signed URL

Create signed url to download file without requiring permissions. This URL can be valid for a set number of seconds.

  • Policy permissions required:
    • buckets permissions: none
    • objects permissions: select

final String signedUrl = await supabase
  .storage
  .from('avatars')
  .createSignedUrl('avatar1.png', 60);

Retrieve public URL

Retrieve URLs for assets in public buckets

  • The bucket needs to be set to public, either via updateBucket() or by going to Storage on app.supabase.com, clicking the overflow menu on a bucket and choosing "Make public"
  • Policy permissions required:
    • buckets permissions: none
    • objects permissions: none

final String publicUrl = supabase
  .storage
  .from('public-bucket')
  .getPublicUrl('avatar1.png');

Release Notes

dart this is the release notes file.#