supabase-rp 0.7.0

One entry point for Supabase from Rust: REST, auth, storage, functions, realtime, and GraphQL
Documentation

supabase-rp

crates.io docs.rs

One entry point for Supabase from Rust. Client holds your project URL, your API key, and one shared HTTP connection pool. It gives you REST, auth, storage, edge functions, GraphQL, and realtime configuration.

Install

[dependencies]
supabase-rp = { version = "0.7", features = ["full"] }
serde = { version = "1", features = ["derive"] }
tokio = { version = "1", features = ["macros", "rt-multi-thread"] }
Feature Default Enables
rest yes Client::rest, Client::from (PostgREST)
auth yes Client::auth (Supabase Auth)
file-session-store no File-backed auth sessions (enables auth)
storage no Client::storage (buckets, objects, signed URLs)
functions no Client::functions (edge functions)
realtime no Client::realtime_config (websocket channels)
graphql no Client::graphql with typed data and partial errors
rustls yes rustls for HTTP and WebSocket connections
native-tls no Native TLS when default features are disabled
typed no Re-exports rp-supabase-client as supabase_rp::typed (schema runtime only)
typed-client no typed plus the client API of rp-supabase-client (enables serde_json/arbitrary_precision)
test-util no rp-postgrest/test-util, for example postgrest::Error::from_response in tests
full no All services, typed-client, and file session storage; does not select a TLS backend

Each enabled crate is re-exported as a module (see Module map). You need only this one dependency.

The default HTTP client of Client::new follows up to 10 redirects, and only to the same origin (scheme, host, port), so credentials never reach another host. Client::new_with_client uses your client and its redirect policy unchanged.

HTTP reads retry explicit 429 responses twice by default. The policy honors Retry-After without shortening server delays. Mutations, streaming request bodies, transport failures, and other HTTP statuses are not replayed by default. Use Client::with_retry_policy to change this policy. RetryPolicy::none() disables it.

For native TLS, use default-features = false, features = ["full", "native-tls"]. rustls takes precedence when both backends are enabled. A supplied HTTP client keeps its own TLS policy.

Quickstart

Pass the project root URL, for example https://abc.supabase.co/, and the project API key. A URL with a path, such as https://gateway.example/supabase, is rejected.

use supabase_rp::Client;

#[derive(Debug, serde::Deserialize)]
struct Todo {
    id: i64,
    title: String,
}

# #[cfg(feature = "rest")]
# async fn run() -> Result<(), Box<dyn std::error::Error>> {
let client = Client::new("https://abc.supabase.co/", "your-anon-key")?;
let todos = client.from("todos").select("id,title").fetch::<Vec<Todo>>().await?;
println!("{todos:?}");
# Ok(())
# }

String-based writes

Use the same builder for reads and writes. _json methods serialize your serde types. Filters apply to updates and deletes. The examples request the affected rows.

# #[cfg(feature = "rest")]
# async fn run(client: supabase_rp::Client) -> Result<(), Box<dyn std::error::Error>> {
#[derive(serde::Serialize)]
struct WriteTodo<'a> { title: &'a str }
#[derive(serde::Deserialize)]
struct Todo { id: i64, title: String }

let created: Vec<Todo> = client.from("todos")
    .insert_json(&WriteTodo { title: "before" }).select("id,title").fetch().await?;
let id = created.first().ok_or("insert returned no rows")?.id.to_string();
let updated: Vec<Todo> = client.from("todos")
    .update_json(&WriteTodo { title: "after" }).eq("id", &id)
    .select("id,title").fetch().await?;
let deleted: Vec<Todo> = client.from("todos").delete().eq("id", &id)
    .select("id,title").fetch().await?;
# Ok(())
# }

Sign in and act as the user

Sign in, then call with_access_token. The new client sends the user token to REST, auth, storage, functions, and GraphQL, so row level security applies. The apikey header keeps the project key. The connection pool is shared.

# #[cfg(all(feature = "rest", feature = "auth"))]
# async fn run(client: supabase_rp::Client) -> Result<(), Box<dyn std::error::Error>> {
use supabase_rp::auth::types::LoginCredentials;

let login = LoginCredentials::email("user@example.com".to_owned(), "password".to_owned());
let session = client.auth().sign_in_with_password(&login).await?;
let token = session.access_token.ok_or("sign-in returned no access token")?;
let user_client = client.with_access_token(&token)?;

let me = user_client.auth().get_user().await?;
println!("{me:?}");
user_client.auth().sign_out().await?;
# Ok(())
# }

Auth also supports anonymous sessions, OAuth PKCE, memory and file session stores, admin user management, and TOTP or phone MFA. See the auth guide. After MFA verification, use the returned session's access token to construct the upgraded user client. Session stores and AAL claim inspection do not replace server-side authorization.

Client::admin(service_role_key) creates an elevated client with explicit server-only credentials. It shares the HTTP pool but does not change the user client. Never send service-role keys to untrusted clients.

Storage

Requires the storage feature.

# #[cfg(feature = "storage")]
# async fn run(client: supabase_rp::Client) -> Result<(), Box<dyn std::error::Error>> {
use supabase_rp::storage::FileOptions;

let avatars = client.storage().from("avatars");
let options = FileOptions {
    content_type: Some("image/png".to_owned()),
    ..FileOptions::default()
};
avatars.upload("user-1/avatar.png", vec![0_u8; 16], &options).await?;
let url = avatars.create_signed_url("user-1/avatar.png", 60).await?;
println!("{url}");
# Ok(())
# }

Separate storage URL

A custom domain can proxy REST and auth but not storage. Call with_storage_url with the storage project root URL. Storage still joins storage/v1. REST, auth, and functions keep the URL from Client::new. An access token from with_access_token stays, in either call order.

# #[cfg(feature = "storage")]
# fn run() -> Result<(), supabase_rp::Error> {
let client = supabase_rp::Client::new("https://api.example.com/", "your-anon-key")?
    .with_storage_url("https://abc.supabase.co/")?;
# Ok(())
# }

Edge functions

Requires the functions feature.

# #[cfg(feature = "functions")]
# async fn run(client: supabase_rp::Client) -> Result<(), Box<dyn std::error::Error>> {
#[derive(serde::Serialize)]
struct Input<'a> {
    name: &'a str,
}

#[derive(Debug, serde::Deserialize)]
struct Output {
    message: String,
}

let reply: Output = client
    .functions()
    .invoke("hello")
    .json(&Input { name: "world" })
    .fetch()
    .await?;
println!("{}", reply.message);
# Ok(())
# }

GraphQL

Requires the graphql feature. Execution errors can accompany partial data, even with HTTP 200. The response preserves both.

# #[cfg(feature = "graphql")]
# async fn run(client: supabase_rp::Client) -> Result<(), Box<dyn std::error::Error>> {
#[derive(Debug, serde::Deserialize)]
struct Data {
    #[serde(rename = "__typename")]
    kind: String,
}
let response = client.graphql().query("{ __typename }").fetch::<Data>().await?;
println!("data: {:?}; errors: {:?}", response.data, response.errors);
# Ok(())
# }

Realtime

Requires the realtime feature. realtime_config returns the configuration that RealtimeConnection::db_changes, presence, and broadcast take. Realtime signs in on its own with LoginCredentials. Sign-in and refresh retries use the returned auth configuration. WebSocket recovery has separate ConnectionOptions. It replays channel joins with the latest token and restores tracked presence. Messages sent during an outage are not replayed.

# #[cfg(feature = "realtime")]
# async fn run(
#     client: supabase_rp::Client,
#     login: supabase_rp::realtime::rp_supabase_auth::types::LoginCredentials,
# ) -> Result<(), Box<dyn std::error::Error>> {
use supabase_rp::realtime::futures::StreamExt as _;
use supabase_rp::realtime::message::phx_join::PostgresChanges;
use supabase_rp::realtime::realtime::{RealtimeConnection, typed_changes};

#[derive(Debug, serde::Deserialize)]
struct Todo {
    id: i64,
    title: String,
}

let (stream, mut channel) = RealtimeConnection::db_changes(client.realtime_config())
    .connect(login)
    .await?;
channel.subscribe_to_changes(vec![PostgresChanges::table("todos")]).await?;
let mut changes = std::pin::pin!(typed_changes::<Todo>(stream));
while let Some(change) = changes.next().await {
    println!("{:?}", change?);
}
# Ok(())
# }

See rp-supabase-realtime for broadcast and presence.

Typed queries from your schema

The typed feature re-exports rp-supabase-client. Generate Rust types for your tables with rp-supabase-codegen, then run typed queries against client.rest(). It is the same Postgrest the typed runtime takes.

typed enables only the schema runtime. Set the codegen runtime_path to ::supabase_rp::typed::schema, and you need no direct rp-supabase-client dependency. Enable typed-client for the client API (authentication helpers and response decoding).

Migration from 0.6

  • Public re-exports move to auth/realtime 0.12, typed client 0.15, and storage 0.3.
  • Regenerate bindings with codegen 0.15. See the typed client migration notes.
  • See the auth migration notes for refresh streams and admin write types.
  • Enable file-session-store or full to use auth::session_store::FileSessionStore. Use a private, trusted parent directory.

Module map

Path Crate Feature
supabase_rp::postgrest rp-postgrest rest
supabase_rp::auth rp-supabase-auth auth
supabase_rp::storage rp-supabase-storage storage
supabase_rp::functions rp-supabase-functions functions
supabase_rp::realtime rp-supabase-realtime realtime
supabase_rp::graphql rp-supabase-graphql graphql
supabase_rp::typed rp-supabase-client typed

The module supabase_rp::auth holds the auth types. The method Client::auth() returns the auth client.

Underlying crates

Crate Purpose
rp-postgrest PostgREST query builder and decoding
rp-postgrest-error Typed PostgREST error codes
rp-supabase-auth Supabase Auth API and token refresh
rp-supabase-storage Storage buckets, objects, signed URLs
rp-supabase-functions Edge function invocation
rp-supabase-graphql GraphQL requests and partial responses
rp-supabase-http Shared TLS and rate-limit retry policy
rp-supabase-realtime Realtime changes, broadcast, presence
rp-supabase-client Typed runtime for generated schemas
rp-supabase-codegen Rust types generated from your database
rp-supabase-mock Mock Supabase server for tests