rust_supabase_sdk
An ergonomic, async Rust client for Supabase.
Mirrors the supabase-js surface area where it makes sense and pushes
Rust-native ergonomics elsewhere:
- PostgREST — chainable query builder (string-typed) and compile-time-checked typed queries via
from_row::<T>()+ codegen-emittedColumn<R, V>constants - Auth — email / phone / OTP / OAuth / anonymous sign-in, account recovery, admin user management, pluggable session stores
- Storage — buckets, object CRUD, signed URLs, image transforms
- RPC — call Postgres functions with
rpc_call(...) - Edge Functions — invoke deployed functions, streaming responses supported
- Realtime — websocket subscriptions to
postgres_changes, broadcast, and presence (opt-in feature) - Retry — automatic exponential backoff on 429 / 5xx
Type safety that catches schema drift before you ship
Two query paths, both first-class. Start with the string-typed builder (zero setup, supabase-js parity). Opt into typed columns whenever you want the compiler to reject wrong column names and wrong value types.
// String path — supabase-js parity, no codegen required
let rows: = client
.from
.select
.eq
.gt
.await?;
// Typed path — same query, every column + value checked at compile time
let rows: = client
.
.eq
.gt
.is_null
.execute
.await?;
Posts and its column constants (Posts::status, Posts::view_count, …) are
emitted by cargo supabase gen types — re-run after a migration and any drift
becomes a compile error. None of the following code will build:
.eq(Users::id, "x") // ✗ wrong row type
.eq(Posts::view_count, "abc") // ✗ view_count is i32, not &str
.is_null(Posts::status) // ✗ status is NOT NULL — is_null requires Option<_>
.like(Posts::view_count, "10%") // ✗ like requires a string-typed column
Runtime cost is zero — Column<R, V> is a &'static str plus a phantom type.
Full design and method list →
Installation
[]
= "0.4.1"
MSRV: Rust 1.75.
Quickstart
use SupabaseClient;
async
Feature flags
| Feature | Default | Notes |
|---|---|---|
postgrest |
✅ | Chainable query builder. |
auth |
✅ | Sign-in flows, OAuth, admin user management. |
storage |
✅ | Buckets + objects + signed URLs. |
functions |
✅ | Edge Functions invocation. |
realtime |
— | Websocket subscriptions (opt-in). |
rustls |
✅ | TLS via rustls (default). |
native-tls |
— | Use OS TLS instead of rustls. |
Enable realtime explicitly:
= { = "0.4.1", = ["realtime"] }
Customizing the client
use Duration;
use ;
let client = builder
.timeout
.retry
.user_agent
.schema
.build;
SupabaseClient is cheap to clone — internal state is Arc-shared, so a single
configured client can be passed across tasks and modules.
Typed queries
The string and typed paths share the same client and the same wire protocol. Pick per query:
| Entry point | Use when | Setup |
|---|---|---|
client.from("posts") |
Ad-hoc queries, views, computed columns, JSON paths, anything codegen can't see | None |
client.from_row::<Posts>() |
You want the compiler to verify column names and value types | cargo supabase gen types (or hand-rolled Row + column constants) |
Codegen output (you don't write this)
use ;
use ;
Available filters on the typed builder
eq, neq, not_eq, gt, gte, lt, lte, like, ilike, not_like,
not_ilike, is_null, is_not_null, is_bool, in_, not_in_, contains,
contained_by, overlaps, order, order_with, limit, offset, range,
count, text_search. Execution: execute, execute_with_count, single,
maybe_single. Escape hatch: .into_untyped() drops to the string-typed
PostgrestBuilder if you need an operation the typed surface doesn't cover.
When the compiler rejects a query
| Misuse | Why |
|---|---|
eq(Users::id, "x") inside from_row::<Posts>() |
Column carries its row type — Users::id is Column<Users, _> |
eq(Posts::view_count, "abc") |
view_count is Column<Posts, i32>, value must be i32 |
is_null(Posts::status) |
status is String (NOT NULL); is_null requires Column<R, Option<V>> |
like(Posts::view_count, "10%") |
like only takes Column<R, String> |
gt(Posts::status, 1i32) |
Value type must match the column's declared type |
Each check is codified as a compile-fail fixture under tests/trybuild/typed-columns/.
Code generation
The companion cargo-supabase binary introspects a Supabase project's PostgREST
schema and emits Rust row structs (with Row impls) ready for use with
from_row::<T>():
Re-run whenever the DB schema changes — drift becomes a compile error rather than a runtime failure.
See docs/codegen.md for the full flag reference, type mapping table, and worked examples.
Testing
Run the test suite:
Code coverage
Install cargo-llvm-cov once:
| Command | Output |
|---|---|
cargo llvm-cov |
Summary in terminal |
cargo llvm-cov --html |
HTML report in target/llvm-cov/html/ |
cargo llvm-cov --open |
HTML report, opened in browser |
cargo llvm-cov --lcov --output-path lcov.info |
LCOV file for CI / coverage services |
Examples
Worked examples for every major surface area:
cargo run --example query
cargo run --example postgrest_typed
cargo run --example auth_email
cargo run --example storage_upload
cargo run --example functions_invoke
cargo run --example realtime_changes --features realtime
All examples read SUPABASE_URL and SUPABASE_API_KEY from the environment.
Documentation
Full API documentation lives on docs.rs.
Contributing
Bug reports, feature requests, and PRs welcome at github.com/Lenard-0/Rust-Supabase-SDK.
License
MIT — see LICENSE.