Kotlin Multiplatform SDK for Supabase — type-safe, coroutine-first, modular client for every platform Kotlin runs on.
Type-safe Result monad — SupabaseResult<T> with map, flatMap, recover — no exceptions leak to callers
Value class IDs — UserId, BucketId, SessionId, ChannelId prevent mixups at compile time
PostgREST filter DSL — eq, neq, gt, like, ilike, in, is, textSearch, contains, and more
OAuth (17 providers) + MFA — TOTP and phone-based multi-factor auth with PKCE flow support
Session management — Auto-refresh, token persistence, SessionState observation via StateFlow
Realtime WebSocket — Phoenix protocol with auto-reconnection, exponential backoff, presence
Edge Functions — Invoke Supabase Edge Functions with typed request/response
Manual composition — Explicit factory helpers for each feature module
15 platform targets — Android, iOS, macOS, tvOS, watchOS, JVM, Linux, Windows, and WasmJs
Add the dependencies you need to your build.gradle.kts:
[versions]
supabase- kmp = " 0.3.0"
[libraries]
supabase- core = { module = " io.github.androidpoet:supabase-core" , version.ref = " supabase-kmp" }
supabase- client = { module = " io.github.androidpoet:supabase-client" , version.ref = " supabase-kmp" }
supabase- auth = { module = " io.github.androidpoet:supabase-auth" , version.ref = " supabase-kmp" }
supabase- database = { module = " io.github.androidpoet:supabase-database" , version.ref = " supabase-kmp" }
supabase- storage = { module = " io.github.androidpoet:supabase-storage" , version.ref = " supabase-kmp" }
supabase- realtime = { module = " io.github.androidpoet:supabase-realtime" , version.ref = " supabase-kmp" }
supabase- functions = { module = " io.github.androidpoet:supabase-functions" , version.ref = " supabase-kmp" }
kotlin {
sourceSets {
commonMain.dependencies {
implementation(libs.supabase.client)
implementation(libs.supabase.auth)
implementation(libs.supabase.database)
implementation(libs.supabase.storage)
implementation(libs.supabase.realtime)
implementation(libs.supabase.functions)
}
}
}
val client = Supabase .create(
projectUrl = " https://your-project.supabase.co" ,
apiKey = " your-anon-key" ,
) {
logging = true
}
val auth = createAuthClient(client)
val database = createDatabaseClient(client)
val storage = createStorageClient(client)
val realtime = createRealtimeClient(client)
val functions = createFunctionsClient(client)
Database — PostgREST with Filter DSL
@Serializable
data class Todo (val id : String , val title : String , val done : Boolean )
@Serializable
data class TodoPatch (val done : Boolean )
@Serializable
data class DashboardStatsRequest (val user_id : String )
@Serializable
data class DashboardStats (val total : Int )
val todos: SupabaseResult <List <Todo >> = database.selectTyped<Todo >(
table = " todos" ,
) {
eq(" done" , " false" )
gt(" priority" , " 3" )
order(" created_at" , ascending = false )
limit(25 )
}
todos.onSuccess { items ->
println (" Got ${items.size} todos" )
}.onFailure { error ->
println (" Error: ${error.message} " )
}
val result = database.insertTyped(
table = " todos" ,
value = Todo (id = " 1" , title = " Ship it" , done = false ),
)
database.updateTyped(
table = " todos" ,
value = TodoPatch (done = true ),
) {
eq(" id" , " 1" )
}
val stats: SupabaseResult <DashboardStats > = database.rpcTyped<DashboardStatsRequest , DashboardStats >(
function = " get_dashboard_stats" ,
request = DashboardStatsRequest (user_id = " 123" ),
)
Auth — Sign In with Session Management
val auth = createAuthClient(client)
val sessionManager = createSessionManager(authClient = auth, supabaseClient = client)
auth.signUpWithEmail(
email = " user@example.com" ,
password = " secure-password" ,
).onSuccess { session ->
sessionManager.saveSession(session)
}
auth.signInWithEmail(
email = " user@example.com" ,
password = " secure-password" ,
).onSuccess { session ->
sessionManager.saveSession(session)
}
val oauthUrl = auth.getOAuthSignInUrl(
provider = OAuthProvider .GOOGLE ,
redirectTo = " myapp://callback" ,
)
val pkce = auth.generatePkceParams()
auth.exchangeCodeForSession(authCode = " code-from-callback" , codeVerifier = pkce.codeVerifier)
val accessToken = sessionManager.accessToken!!
auth.mfaEnroll(factorType = MfaFactorType .TOTP , accessToken = accessToken).onSuccess { factor ->
auth.mfaVerify(
factorId = factor.id,
challengeId = " challenge-id" ,
code = " 123456" ,
accessToken = accessToken,
)
}
sessionManager.sessionState.collect { state ->
when (state) {
is SessionState .Authenticated -> println (" User: ${state.session.user.id} " )
is SessionState .Expired -> println (" Session expired, refreshing..." )
SessionState .NotAuthenticated -> println (" Signed out" )
SessionState .Loading -> println (" Loading..." )
}
}
Storage — File Upload & Download
val storage = createStorageClient(client)
storage.upload(
bucket = " avatars" ,
path = " user123/avatar.png" ,
data = imageBytes,
contentType = " image/png" ,
).onSuccess { key ->
println (" Uploaded: $key " )
}
storage.createSignedUrl(
bucket = " avatars" ,
path = " user123/avatar.png" ,
expiresIn = 3600 ,
).onSuccess { url ->
println (" Signed URL: $url " )
}
val publicUrl = storage.getPublicUrl(bucket = " avatars" , path = " user123/avatar.png" )
storage.list(bucket = " avatars" , prefix = " user123/" ).onSuccess { files ->
files.forEach { println (it.name) }
}
Realtime — WebSocket Subscriptions
val realtime = createRealtimeClient(client)
realtime.connect()
realtime.connectionState.collect { state ->
when (state) {
is ConnectionState .Connected -> println (" Connected" )
is ConnectionState .Reconnecting -> println (" Reconnecting attempt ${state.attempt} ..." )
is ConnectionState .Failed -> println (" Failed: ${state.reason} " )
else -> {}
}
}
val subscription = realtime.channel(" todos" )
.onPostgresChange(table = " todos" , event = PostgresChangeEvent .INSERT ) { record ->
println (" New todo: $record " )
}
.onPostgresChange(table = " todos" , event = PostgresChangeEvent .DELETE ) { record ->
println (" Deleted: $record " )
}
.subscribe()
realtime.channel(" room:lobby" )
.onPresence { state ->
println (" Online users: ${state.size} " )
}
.subscribe()
subscription.broadcast(event = " cursor" , payload = buildJsonObject {
put(" x" , 100 )
put(" y" , 200 )
})
subscription.unsubscribe()
realtime.disconnect()
val functions = createFunctionsClient(client)
functions.invoke(
functionName = " hello-world" ,
body = """ {"name": "Kotlin"}""" ,
).onSuccess { data ->
println (" Response: $data " )
}
functions.invokeTyped<WelcomeResponse >(
functionName = " hello-world" ,
body = """ {"name": "Kotlin"}""" ,
).onSuccess { response ->
println (" Message: ${response.message} " )
}
functions.invokeWithBody(
functionName = " process-image" ,
body = imageBytes,
contentType = " image/png" ,
)
┌─────────────────────────────────────────────────────────────────────┐
│ Your App │
├──────────┬───────────┬───────────┬───────────┬───────────┬─────────┤
│ supabase │ supabase │ supabase │ supabase │ supabase │supabase │
│ auth │ database │ storage │ realtime │ functions │ client │
│ │ │ │ │ │ │
│ OAuth │ PostgREST │ Buckets │ WebSocket │ Invoke │ HTTP │
│ MFA/PKCE │ Filter DSL│ Upload │ Phoenix │ Typed Req │ Auth │
│ Session │ RPC │ SignedURL │ Presence │ Response │ Factory │
│ Manager │ Typed Ext │ Public URL│ Reconnect │ Binary │ Config │
├──────────┴───────────┴───────────┼───────────┤ │ │
│ │ Ktor │ │ │
│ │ Engines │ │ │
├───────────────────────────────────┴───────────┤ │ │
│ supabase-core │ │ │
│ SupabaseResult · Value IDs · Filter DSL │ │ │
│ Error Types · Response Models │ │ │
└────────────────────────────────────────────────┴───────────┴─────────┘
Module
Artifact
Description
supabase-core
io.github.androidpoet:supabase-core
Result monad, error types, value class IDs, filter DSL
supabase-client
io.github.androidpoet:supabase-client
HTTP transport, platform engines, auth state, factory wiring
supabase-auth
io.github.androidpoet:supabase-auth
Email, phone, OTP, OAuth (17 providers), MFA, PKCE, session management
supabase-database
io.github.androidpoet:supabase-database
PostgREST CRUD, RPC, typed filter extensions
supabase-storage
io.github.androidpoet:supabase-storage
Bucket CRUD, file upload/download, signed & public URLs
supabase-realtime
io.github.androidpoet:supabase-realtime
WebSocket (Phoenix protocol), auto-reconnect, broadcast, presence
supabase-functions
io.github.androidpoet:supabase-functions
Edge function invocation with typed responses
Platform
Target
Ktor Engine
Android
androidTarget()
OkHttp
JVM
jvm()
OkHttp
iOS
iosX64() iosArm64() iosSimulatorArm64()
Darwin
macOS
macosX64() macosArm64()
Darwin
tvOS
tvosX64() tvosArm64() tvosSimulatorArm64()
Darwin
watchOS
watchosX64() watchosArm64() watchosSimulatorArm64()
Darwin
Linux
linuxX64()
CIO
Windows
mingwX64()
CIO
Web
wasmJs()
Js
supabase-kmp
supabase-kt (official)
Codebase
~3K LOC
~26K LOC
Error handling
SupabaseResult<T> monad
Thrown exceptions
Type safety
Value class IDs
String IDs
Dependencies
3 core
7+
Session mgmt
SessionManager + StateFlow
Built-in (heavier)
Reconnection
Exponential backoff
Exponential backoff
Targets
15
15+
# Compile all targets
./gradlew compileKotlinJvm
# Run tests
./gradlew jvmTest
# Full build (all platforms)
./gradlew build --no-configuration-cache
# Publish to Maven Central (CI only, auto-release)
./gradlew publishAndReleaseToMavenCentral --no-configuration-cache
Build Docs Locally (MkDocs)
python -m pip install -r requirements-docs.txt
mkdocs serve
Publish Docs to GitHub Pages (MkDocs)
Automated via GitHub Actions workflow:
Manual publish:
MIT License
Copyright (c) 2026 Ranbir Singh
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.