Supabase Logo

supabase

Dart client library for Supabase, for use in non-Flutter Dart environments.

Guides · Reference Docs

pub package pub test

supabase is the Supabase client for Dart code that does not run in Flutter, such as a server or a command line tool. It covers typed database queries, auth, realtime, storage and edge functions.

Important

For a Flutter app, use supabase_flutter instead. It wraps this package and adds session persistence, deep links and the app lifecycle.

This README describes v3. Most Supabase Dart examples published elsewhere are v2 code, which no longer compiles. To upgrade a v2 project, follow MIGRATION.md.

Using an AI coding agent

supabase ships package skills, instructions that teach coding agents such as Claude Code, Cursor, Gemini or Copilot the current API. Agents trained on v2 examples otherwise tend to write code that does not compile against v3. The skills for generating the schema and for testing ship with the dev dependencies they are about. Install the skills of every package you use:

dart run skills@ get -p supabase
dart run skills@ get -p supabase_typegen
dart run skills@ get -p supabase_test
Skill Package Covers
supabase-idioms supabase Writing or reviewing any code that uses the client
supabase-realtime supabase Live lists with stream(), onTableChanges, broadcast and presence
supabase-migrate-v3 supabase Upgrading a project from v2 to v3
supabase-typegen-generate supabase_typegen Generating the typed database schema and keeping it in sync
supabase-test-testing supabase_test Unit tests with supabase_test

Run the commands again after upgrading the packages to update the skills. Commit the installed skills together with .config/dart_skills/ so everyone working on the project gets the same version.

Getting started

1. Add the package

dart pub add supabase

2. Create the client

Find the URL and the publishable key of your project in the dashboard under Project Settings > API Keys. Create one client when the program starts and reuse it, as described in Server-side usage.

import 'package:supabase/supabase.dart';

final supabase = SupabaseClient(
  'https://your-project.supabase.co',
  'your-publishable-key',
);

The client keeps the session, and the code verifiers of the default PKCE flow, in memory for as long as the process runs. Pass an AuthAsyncStorage as the asyncStorage of AuthClientOptions when a sign-in has to survive a restart, such as one completed from an email link.

To persist the session as well, set persistSession: true next to the storage. The client throws when it is created with persistSession: true and no storage, unless it uses an accessToken callback.

3. Generate the typed schema

Queries are typed by a Dart file generated from your database schema. It holds a row type per table, BooksRow for a books table, the BooksInsert and BooksUpdate values that writes accept, and a Books class with the table definition and one constant per column. Generate it with the Supabase CLI, version 3.0.0 or newer, from the directory of your pubspec.yaml:

dart pub add dev:supabase_typegen
supabase gen types --lang dart > lib/supabase_schema.g.dart

By default the CLI reads the schema of the stack started with supabase start. Use --remote for the project linked with supabase link, or --remote=<ref> for any hosted project. Never edit the generated file: regenerate it after every change to the database schema, whether from a migration or the dashboard, and after upgrading supabase_typegen or the Supabase CLI, and commit it. The supabase_typegen README lists everything it generates.

4. Query

final List<BooksRow> books = await supabase
    .table(Books.table)
    .select()
    .where(Books.inPrint.isTrue() & Books.price.lt(10))
    .order(Books.title)
    .limit(20);

await supabase
    .table(Books.table)
    .insert(BooksInsert(authorId: 2, title: 'Dune'));

await supabase
    .table(Books.table)
    .update(BooksUpdate(inPrint: false))
    .where(Books.id.eq(1));

final BooksRow? book = await supabase.rpc.bookById(bookId: 1);

The supabase_flutter README shows the rest of the API, which is the same in both packages: embedding related rows, realtime, auth, storage, edge functions and errors.

Rules of the v3 API

  • Query tables with table() and the generated schema. from() with Map<String, dynamic> rows is only the fallback for a table that is not in the generated file.
  • Realtime events arrive on streams. No method takes a callback:.
  • A typed update() or delete() sends nothing until .where(…) or .all() is called on it.
  • Catch the SupabaseException hierarchy, never ClientException, TimeoutException or String.
  • Create one SupabaseClient per process, not one per request.

Supabase Service Compatibility

supabase only guarantees compatibility with the latest release of each Supabase service, unless a minimum supported version is explicitly listed below.

Older service releases are supported on a best-effort basis, but compatibility is not guaranteed.

If you self-host Supabase, we recommend upgrading your services to their latest releases when upgrading the SDK.

Service SDK package Server releases Lowest supported version
Auth supabase_auth supabase/auth Latest release
Storage supabase_storage supabase/storage Latest release
Functions supabase_functions supabase/edge-runtime Latest release
PostgREST postgrest PostgREST/postgrest Latest release
Realtime supabase_realtime supabase/realtime Latest release

Docs

Server-side usage

Create one SupabaseClient when the process starts and reuse it for as long as the process lives. The client keeps a pool of HTTP connections, and a request on a warm connection skips the TCP handshake and, over HTTPS, the TLS handshake, which on a server talking to a remote Supabase project take longer than the request itself. A client created per request throws that pool away every time.

To act on behalf of the user of an incoming request, keep the shared client and pass the access token of that request as the Authorization header of the call. The client only adds its own Authorization header when the request has none:

import 'package:my_app/supabase_schema.g.dart';
import 'package:supabase/supabase.dart';

Future<List<BooksRow>> booksOfUser(
  SupabaseClient supabase,
  String userAccessToken,
) {
  return supabase
      .table(Books.table)
      .select()
      .setHeader('Authorization', 'Bearer $userAccessToken');
}

Storage and functions calls accept a header the same way, through setHeader() on a bucket and the headers parameter of invoke().

The transport a SupabaseClient creates for itself closes a connection that has been unused for 60 seconds, which is below the point where the Supabase gateway closes it from its side. Open connections keep a Dart program alive until then, so call dispose() when the process shuts down.

If you do need more than one SupabaseClient, pass the same http.Client to each of them so they share one pool. Disposing a SupabaseClient leaves a client you passed in open, so close it yourself at shutdown. dart:io defaults to a 15 second idle timeout, so raise it:

import 'dart:io';

import 'package:http/io_client.dart';

final httpClient = IOClient(
  HttpClient()..idleTimeout = const Duration(seconds: 60),
);

Request ids and tracing

The exceptions thrown by the auth, database, storage and functions clients carry the requestId the Supabase gateway assigned to the failed request, which finds it in the logs of your project. To correlate requests with your own traces, pass TracePropagationOptions to SupabaseClient. Both are described, with Sentry and OpenTelemetry recipes, in the supabase_flutter README.

License

This repo is licensed under MIT.

Credits

Libraries

supabase
A dart client for Supabase. It supports database query, authenticate users and listen for realtime changes. This client makes it simple for developers to build secure and scalable products.