πŸŽ‰ Native Google & Apple sign-in is here β†’ read the guide
Getting Started

Getting Started

This page takes you from an empty project to your first successful call to Supabase. No prior experience with the library is assumed β€” we’ll explain each piece as it appears.

How the pieces fit together

Supabase KMP is split into small modules so you only ship what you use. There’s one client that holds your project URL, your key and the HTTP machinery β€” and then a feature client for each thing you want to do:

                β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
                β”‚  SupabaseClient   β”‚  ← created once, holds URL + key + HTTP
                β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”˜
        β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
     auth      database  storage  realtime  functions   ← built from the client
   (sign in)   (rows)    (files)  (live)    (server fns)

You always create the SupabaseClient first, then build the feature clients you need from it. Let’s do that.

Install

Supabase KMP is published to Maven Central under io.github.androidpoet. Add the version and the modules you need to your version catalog. The catalog (libs.versions.toml) is just a list of dependencies with friendly names β€” nothing Supabase-specific.

gradle/libs.versions.toml
[versions]
supabase-kmp = "0.7.0"
 
[libraries]
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" }
build.gradle.kts
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)
        }
    }
}

Only depend on the modules you use β€” each one is published independently, so a small app can pull in just supabase-client + supabase-auth and add the rest later. supabase-client is always required; it’s the foundation the others build on.

Create a client

Everything starts from a SupabaseClient. Create one with your project URL and anon key, then build the feature clients you need from it.

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)

You’ll find both values in the Supabase dashboard under Project Settings β†’ API. The trailing { } block is optional configuration β€” here we turn on logging so you can see requests in the console while learning.

⚠️

Use the anon (β€œanonymous”) key in client apps β€” never the service-role key. The anon key is safe to ship because your database’s Row Level Security rules decide what each user can read or write. The service-role key bypasses those rules, so it belongs only on a trusted server (that’s what the supabase-auth-admin module is for).

Make your first call

Every call returns a SupabaseResult β€” a small wrapper that is either a success (with your value) or a failure (with an error). You don’t use try/catch; you branch with onSuccess and onFailure:

@Serializable
data class Todo(val id: String, val title: String, val done: Boolean)
 
database.selectTyped<Todo>(table = "todos")
    .onSuccess { todos -> println("Loaded ${todos.size} todos") }
    .onFailure { error -> println("Something went wrong: ${error.message}") }

That’s the whole rhythm of the library: call a function, get a SupabaseResult, handle both outcomes. It’s worth understanding well β€” see Results & Errors next.

Next steps

Learn the error model

Read Results & Errors β€” it’s the one concept everything else builds on.

Authenticate a user

Head to Authentication for email, phone, OTP, OAuth and native sign-in.

Read and write data

See Database for the typed PostgREST DSL.

Go realtime

Subscribe to live changes with Realtime.