supabase 3.0.0-dev.16
supabase: ^3.0.0-dev.16 copied to clipboard
A dart client for Supabase. This client makes it simple for developers to build secure and scalable products.
supabase
Dart client library for Supabase, for use in non-Flutter Dart environments.
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()withMap<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()ordelete()sends nothing until.where(…)or.all()is called on it. - Catch the
SupabaseExceptionhierarchy, neverClientException,TimeoutExceptionorString. - Create one
SupabaseClientper 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.