🎉 Native Google & Apple sign-in is here → read the guide
Configuration

Configuration

When you create a client you can hand it a few settings — how chatty its logs are, how it retries failed requests, extra headers to send, and hooks for tracing. You set these in the optional { } block at the end of Supabase.create. The good news: everything has a sensible default, so a brand-new app can ignore this page entirely and come back when it needs to tune something.

The trailing { } lambda is a SupabaseConfigBuilder — it tunes logging, retries, custom headers, and observability hooks. You only set what you need.

val client = Supabase.create(
    projectUrl = "https://your-project.supabase.co",
    apiKey = "your-anon-key",
) {
    logging = true
    logLevel = LogLevel.HEADERS
    headers["X-App-Version"] = "1.4.0"
    retry = RetryConfig(maxRetries = 5)
    logger = MyTimberLogger()
    interceptor = MyTelemetryInterceptor()
}
OptionTypeDefaultPurpose
loggingBooleanfalseEnable HTTP wire logging.
logLevelLogLevelNONEKtor verbosity (io.ktor.client.plugins.logging.LogLevel): NONE, INFO, HEADERS, BODY, ALL.
headersMutableMapemptyExtra headers sent on every request.
retryRetryConfigRetryConfig.DefaultBackoff policy for transient failures.
loggerSupabaseLogger?nullRoute wire logs into your logging framework.
interceptorSupabaseInterceptor?nullTelemetry hooks around every request.

Retries

Retries are opt-in per call — read methods like PostgREST select default to retry = true because reads are idempotent; mutations don’t retry unless you ask. RetryConfig controls how a retried call backs off and which statuses count as transient.

retry = RetryConfig(
    maxRetries = 3,                                  // attempts after the initial try
    baseDelayMillis = 1_000,                         // first backoff; doubles each retry
    maxDelayMillis = 30_000,                         // cap on a single delay
    retryableStatuses = setOf(429, 500, 502, 503, 504, 520),
)

Backoff is exponential — min(maxDelayMillis, baseDelayMillis shl attempt) — so with the defaults a retried call waits ~1s, 2s, then 4s. A Retry-After response header always wins over the computed delay.

RetryConfig.Default is 3 retries, 1s base, 30s cap, retrying 429 and the transient 5xx codes. The constructor validates its inputs (non-negative delays, maxDelayMillis >= baseDelayMillis).

Logging

Set logging = true and pick a logLevel to see requests on the console. To send those lines somewhere structured — Timber on Android, OSLog on iOS, SLF4J on the JVM — provide a SupabaseLogger. Verbosity is still governed by logLevel; the logger only changes where the lines go.

class TimberLogger : SupabaseLogger {
    override fun log(level: SupabaseLogLevel, message: String, throwable: Throwable?) {
        when (level) {
            SupabaseLogLevel.DEBUG -> Timber.d(throwable, message)
            SupabaseLogLevel.INFO  -> Timber.i(throwable, message)
            SupabaseLogLevel.WARN  -> Timber.w(throwable, message)
            SupabaseLogLevel.ERROR -> Timber.e(throwable, message)
        }
    }
}

Observability

A SupabaseInterceptor gives you telemetry hooks around every request — emit spans, record latency histograms, count error categories, attach correlation IDs. All methods are suspend (so they may do async work) and default to no-ops, so you implement only what you need.

class TelemetryInterceptor : SupabaseInterceptor {
    override suspend fun onRequest(method: String, url: String) {
        tracer.startSpan("$method $url")
    }
 
    override suspend fun onResponse(method: String, url: String, statusCode: Int, durationMillis: Long) {
        metrics.recordLatency(url, durationMillis, statusCode)
    }
 
    override suspend fun onError(method: String, url: String, error: SupabaseError, durationMillis: Long) {
        metrics.countError(error.category)
    }
}

Exactly one terminal hook fires per call: onResponse when an HTTP response is received (any status — a 404 is a response, not an error), or onError when no response was obtained (offline, DNS/TLS failure, timeout).

⚠️

Hooks are observational — they must not throw, and they cannot alter the request or its result. Keep them fast; they run on the request path.