supabase-rust is a light Rust wrapper around the Supabase REST API.
- Client creation with validation
- Unified error type (
supabase_rust::Error) - Sign-in email/pass (returns typed
AuthResponse) - Signup email/pass
- Signup phone/pass
- Token refresh
- Logout
- Verify one-time token
- Authorize external OAuth provider
- Password recovery
- Resend one-time password over email or SMS
- Magic link authentication
- One-time password authentication
- Retrieval of user's information
- Reauthentication of a password change
- Enrollment of MFA
- MFA challenge and verify
- OAuth callback
- All SSO
- All Admin
- Database support (CRUD operations with fluent query builder)
Add the following dependency to your Cargo.toml:
[dependencies]
supabase-rust = "0.3"
tokio = { version = "1", features = ["full"] }
serde = { version = "1.0", features = ["derive"] }You can initialize the client with explicit values or via environment variables.
Supabase::new() returns a Result and will return an error if the URL or API
key is missing.
use supabase_rust::Supabase;
// Option 1: Using environment variables
// Set SUPABASE_URL, SUPABASE_API_KEY, and optionally SUPABASE_JWT_SECRET
let client = Supabase::new(None, None, None)?;
// Option 2: Explicit configuration
let client = Supabase::new(
Some("https://your-project.supabase.co"),
Some("your-api-key"),
Some("your-jwt-secret"),
)?;Auth methods return typed responses: AuthResponse (with access_token,
refresh_token, etc.) or EmptyResponse (with status). Non-2xx responses
are automatically converted to Error::Api.
use supabase_rust::{Supabase, AuthResponse, Error};
#[tokio::main]
async fn main() -> Result<(), Error> {
let client = Supabase::new(None, None, None)?;
// Sign up a new user
let auth: AuthResponse = client
.signup_email_password("user@example.com", "password123")
.await?;
// Sign in with email and password
let auth: AuthResponse = client
.sign_in_password("user@example.com", "password123")
.await?;
// Access tokens directly from the typed response
println!("Access token: {}", auth.access_token);
println!("Refresh token: {}", auth.refresh_token);
// Refresh an access token
let refreshed: AuthResponse = client.refresh_token(&auth.refresh_token).await?;
// Validate a JWT token (synchronous)
let claims = client.jwt_valid(&auth.access_token)?;
println!("User email: {}", claims.email);
// Logout (requires a bearer token)
let mut client = client;
client.set_bearer_token(&auth.access_token);
let resp = client.logout().await?;
println!("Logged out with status: {}", resp.status);
Ok(())
}The library provides a fluent query builder for PostgREST database operations.
The QueryBuilder is annotated with #[must_use] so the compiler will warn if
you forget to call .execute().
use supabase_rust::{Supabase, Error};
use serde::{Deserialize, Serialize};
#[derive(Debug, Serialize, Deserialize)]
struct User {
id: i64,
name: String,
email: String,
status: String,
}
#[derive(Debug, Serialize)]
struct NewUser {
name: String,
email: String,
status: String,
}
#[tokio::main]
async fn main() -> Result<(), Error> {
let client = Supabase::new(None, None, None)?;
// SELECT: Get all users (automatic JSON deserialization)
let users: Vec<User> = client
.from("users")
.select("*")
.execute_and_parse()
.await?;
// SELECT: Get specific columns with filters
let response = client
.from("users")
.select("id,name,email")
.eq("status", "active")
.order("name")
.limit(10)
.execute()
.await?;
// INSERT: Create a new record
let new_user = NewUser {
name: "John Doe".to_string(),
email: "john@example.com".to_string(),
status: "active".to_string(),
};
let response = client
.from("users")
.insert(&new_user)?
.execute()
.await?;
// UPDATE: Modify existing records
let updates = serde_json::json!({
"status": "inactive"
});
let response = client
.from("users")
.update(&updates)?
.eq("id", "123")
.execute()
.await?;
// DELETE: Remove records
let response = client
.from("users")
.delete()
.eq("id", "123")
.execute()
.await?;
Ok(())
}| Method | Description | PostgREST Equivalent |
|---|---|---|
eq(col, val) |
Equal | col=eq.val |
neq(col, val) |
Not equal | col=neq.val |
gt(col, val) |
Greater than | col=gt.val |
gte(col, val) |
Greater than or equal | col=gte.val |
lt(col, val) |
Less than | col=lt.val |
lte(col, val) |
Less than or equal | col=lte.val |
like(col, pattern) |
Pattern match (use * as wildcard) |
col=like.pattern |
ilike(col, pattern) |
Case-insensitive pattern match | col=ilike.pattern |
in_(col, &[vals]) |
Value in list | col=in.(v1,v2,v3) |
is_null(col) |
Is null | col=is.null |
not_null(col) |
Is not null | col=not.is.null |
| Method | Description |
|---|---|
order(col) |
Order by column (use col.desc for descending) |
limit(n) |
Limit number of rows |
offset(n) |
Skip first n rows |
Filters can be chained to create complex queries:
let response = client
.from("products")
.select("id,name,price,category")
.gte("price", "10")
.lte("price", "100")
.neq("status", "discontinued")
.in_("category", ["electronics", "accessories"])
.order("price.desc")
.limit(20)
.execute()
.await?;All operations return Result<T, supabase_rust::Error>. The error type has the
following variants:
| Variant | Description |
|---|---|
Error::Config(String) |
Missing URL or API key at construction |
Error::Request(reqwest::Error) |
HTTP transport failure |
Error::Serialization(serde_json::Error) |
JSON serialization/deserialization failure |
Error::Jwt(jsonwebtoken::errors::Error) |
JWT validation failure |
Error::AuthRequired(String) |
Operation requires a bearer token |
Error::Api { status, message } |
Non-2xx HTTP response from Supabase |
The Supabase team has an outline of their OpenAPI specs over in this yaml file.
Supabase-rust is available under the MIT license, see the LICENSE file for more information.