Kotlin SDK for interacting with the PocketBase Web API.
To add this SDK to your Kotlin Multiplatform project:
kotlin {
sourceSets {
val commonMain by getting {
dependencies {
implementation("io.pocketbase:pocketbase:0.1.10")
}
}
}
}
val pb = PocketBase("http://127.0.0.1:8090")
// authenticate as regular user
val userData = pb.collection<User>("users").authWithPassword(
usernameOrEmail = "test@example.com",
password = "123456",
)
// list and filter "example" collection records
val result = pb.collection<Example>("example").getList(
page = 1,
perPage = 20,
filter = "status = true && created >= \"2022-08-01\"",
sort = "-created",
expand: "someRelField",
);
// subscribe to realtime "example" collection changes
pb.collection<Example>("example").subscribe(
topic = "*",
filter = "someField > 10",
callback = { e ->
print(e.action) // create, update, delete
print(e.record) // the changed record
},
)
// and much more...
More detailed API docs and copy-paste examples could be found in the API documentation for each service or in the Services section below.
PocketBase Kotlin SDK handles file upload seamlessly by using ktor submitFormWithBinaryData
Here is a simple example of uploading a single text file together with some other regular fields:
val pb = PocketBase("http://127.0.0.1:8090")
val record = pb.collection<Example>("Example").create(
body = Example(
title = "Hello world!",
// ... any other regular field
),
files = listOf(
File(
field = "document", // the name of the file field
fileName = "example_document.txt",
data = "example content...".toByteArray(),
),
),
)
print(record.document) // "example_document_$id.txt"
The SDK comes with several helpers to make it easier working with the RecordService
and RecordModel
DTO.
Leverage Kotlin's type system for safer API interactions.
below is an example how to access and cast record data values:
@Serializable
data class Example(
val options: List<String>,
val email: String,
val status: Boolean,
val total: Int,
val price: Double,
val nested: String = "missing",
)
val record = pb.collection<Example>('example').getOne("RECORD_ID")
All services return a standard Future-based response, so the error handling is straightforward:
pb.collection('example').getList(page: 1, perPage: 50).then((result) {
// success...
print('Result: $result');
}).catchError((error) {
// error...
print('Error: $error');
});
// OR if you are using the async/await syntax:
try {
final result = await pb.collection('example').getList(page: 1, perPage: 50);
} catch (error) {
print('Error: $error');
}
All response errors are normalized and wrapped as ClientException
with the following public members that you could use:
data class ClientException(
val url: String // The address of the failed request
val statusCode: Int // The status code of the failed request
val data: JsonObject // The JSON API error response
val originError: String // The original response error
)
The SDK keeps track of the authenticated token and auth model for you via the pb.authStore
service.
The default AuthStore
class has the following public members that you could use:
AuthStore {
token: String // Getter for the stored auth token
model: RecordModel|AdminModel|null // Getter for the stored auth RecordModel or AdminModel
isValid Boolean // Getter to loosely check if the store has an existing and unexpired token
onChange Flow<AuthStoreEvent> // Flow that gets triggered on each auth store change
// methods
save(token, model) // update the store with the new auth data
clear() // clears the current auth store state
}
To "logout" an authenticated record or admin, you can just call pb.authStore.clear()
.
To "listen" for changes in the auth store, you can listen to the onChange
collect flow:
pb.authStore.onChange.collect { e ->
print(e.token)
print(e.model)
}
The default AuthStore
is not persistent!
If you want to persist the AuthStore
state (eg. in case the app get closed), you can extend the default store and pass a new custom instance as constructor argument to the client.
To make it slightly easier, the SDK has a builtin AsyncAuthStore
that you can combine with any async persistent layer (multiplatform-settings
, DataStore
, local file, etc.).
Here is an example using multiplatform-settings
:
@Serializable
data class User(
val username: String,
val name: String,
val verified: Boolean,
): RecordModel()
class MultiplatformSettingsAuthStore(
private val settings: Settings,
) {
val token: String?
get() = settings.getStringOrNull(TOKEN_KEY)
fun save(token: String?) {
settings[TOKEN_KEY] = token
}
private companion object {
const val TOKEN_KEY = "token"
}
}
val mpSettingsAuthStore = MultiplatformSettingsAuthStore(
settings = get(), // expect val settings: Settings
)
val store = RecordAsyncAuthStore(
cls = User::class,
save = mpSettingsAuthStore::save,
clear = { mpSettingsAuthStore.save(null) },
initial = mpSettingsAuthStore.token,
)
val pb = PocketBase("http://example.com", authStore = store)
The SDK comes with a helper pb.filter(expr, params)
method to generate a filter string with placeholder parameters ({:paramName}
) populated from a Map
.
val records = pb.collection<Example>("example").getList(filter = pb.filter(
// the same as: "title ~ 'exa\\'mple' && created = '2023-10-18 18:20:00.123Z'"
expr = "title ~ {:title} && created >= {:created}",
query = mapOf("title" to "exa'mple", "created" to Clock.System.now()),
));
The supported placeholder parameter values are:
String
(single quotes are autoescaped)Instant
(kotlinx.datetime)Boolean
Number
null
json.encodeToString()
// Returns a paginated records list.
🔓 pb.collection<T: RecordModel>(collectionIdOrName).getList(
page: Int = 1,
perPage: Int = 30,
skipTotal: Boolean = false,
expand: String? = null,
filter: String? = null,
sort: String? = null,
fields: String? = null,
query: Map<String, Any?> = emptyMap(),
headers: Map<String, String> = emptyMap(),
)
// Returns a list with all records batch fetched at once.
🔓 pb.collection<T: RecordModel>(collectionIdOrName).getFullList(
batch: Int = 500,
expand: String? = null,
filter: String? = null,
sort: String? = null,
fields: String? = null,
query: Map<String, Any?> = emptyMap(),
headers: Map<String, String> = emptyMap(),
)
// Returns the first found record matching the specified filter.
🔓 pb.collection<T: RecordModel>(collectionIdOrName).getFirstListItem(
filter: String,
expand: String? = null,
fields: String? = null,
query: Map<String, Any?> = emptyMap(),
headers: Map<String, String> = emptyMap(),
)
// Returns a single record by its id.
🔓 pb.collection<T: RecordModel>(collectionIdOrName).getOne(
id: String,
expand: String? = null,
fields: String? = null,
query: Map<String, Any?> = emptyMap(),
headers: Map<String, String> = emptyMap(),
)
// Creates (aka. register) a new record.
🔓 pb.collection<T: RecordModel>(collectionIdOrName).create(
body: T? = null,
expand: String? = null,
fields: String? = null,
query: Map<String, Any?> = emptyMap(),
headers: Map<String, String> = emptyMap(),
files: List<File> = emptyList(),
)
// Updates an existing record by its id.
🔓 pb.collection<T: RecordModel>(collectionIdOrName).update(
id: String,
body: T? = null,
expand: String? = null,
fields: String? = null,
query: Map<String, Any?> = emptyMap(),
headers: Map<String, String> = emptyMap(),
)
// Deletes a single record by its id.
🔓 pb.collection<T: RecordModel>(collectionIdOrName).delete(
id: String,
body: T? = null,
query: Map<String, Any?> = emptyMap(),
headers: Map<String, String> = emptyMap(),
)
// Subscribe to realtime changes to the specified topic ("*" or recordId).
//
// It is safe to subscribe multiple times to the same topic.
//
// You can use the returned UnsubscribeFunc to remove a single registered subscription.
// If you want to remove all subscriptions related to the topic use unsubscribe(topic).
🔓 pb.collection<T>(collectionIdOrName)subscribe(
topic: String,
callback: (event: RecordSubscriptionEvent<T>) -> Unit,
expand: String? = null,
filter: String? = null,
fields: String? = null,
query: Map<String, Any?> = emptyMap(),
headers: Map<String, String> = emptyMap(),
)
// Unsubscribe from all registered subscriptions to the specified topic ("*" or recordId).
// If topic is not set, then it will remove all registered collection subscriptions.
🔓 pb.collection<T>(collectionIdOrName).unsubscribe(topic: String = "")
Available only for "auth" type collections.
// Returns all available application auth methods.
🔓 pb.collection(collectionIdOrName).listAuthMethods(
query: Map<String, Any?> = emptyMap(),
headers: Map<String, String> = emptyMap(),
)
// Authenticates a record with their username/email and password.
🔓 pb.collection(collectionIdOrName).authWithPassword(
usernameOrEmail: String,
password: String,
expand: String? = null,
fields: String? = null,
body: Map<String, Any?> = emptyMap(),
query: Map<String, Any?> = emptyMap(),
headers: Map<String, String> = emptyMap(),
)
// Refreshes the current authenticated record model and auth token.
🔐 pb.collection(collectionIdOrName).authRefresh(
expand: String? = null,
fields: String? = null,
body: Map<String, Any?> = emptyMap(),
query: Map<String, Any?> = emptyMap(),
headers: Map<String, String> = emptyMap(),
)
// Sends a user password reset email.
🔓 pb.collection(collectionIdOrName).requestPasswordReset(
email: String,
body: Map<String, Any?> = emptyMap(),
query: Map<String, Any?> = emptyMap(),
headers: Map<String, String> = emptyMap(),
)
// Confirms a record password reset request.
🔓 pb.collection(collectionIdOrName).confirmPasswordReset(
passwordResetToken: String,
password: String,
passwordConfirm: String,
body: Map<String, Any?> = emptyMap(),
query: Map<String, Any?> = emptyMap(),
headers: Map<String, String> = emptyMap(),
)
// Sends a record verification email request.
🔓 pb.collection(collectionIdOrName).requestVerification(
email: String,
body: Map<String, Any?> = emptyMap(),
query: Map<String, Any?> = emptyMap(),
headers: Map<String, String> = emptyMap(),
)
// Confirms a record email verification request.
🔓 pb.collection(collectionIdOrName).confirmVerification(
verificationToken: String,
body: Map<String, Any?> = emptyMap(),
query: Map<String, Any?> = emptyMap(),
headers: Map<String, String> = emptyMap(),
)
// Builds and returns an absolute record file url for the provided filename.
🔓 pb.files.getUrlgetUrl(
record: RecordModel,
fileName: String,
thumb: String? = null,
token: String? = null,
download: Boolean? = null,
query: Map<String, Any?> = emptyMap(),
)
// Requests a new private file access token for the current auth model (admin or record).
🔐 pb.files.getToken(
body: @Serializable Any? = null,
query: Map<String, Any?> = emptyMap(),
headers: Map<String, String> = emptyMap(),
)
// Authenticates an admin account by its email and password.
🔓 pb.admins.authWithPassword(
email: String,
password: String,
body: Map<String, Any?> = emptyMap(),
query: Map<String, Any?> = emptyMap(),
headers: Map<String, String> = emptyMap(),
)
// Refreshes the current admin authenticated model and token.
🔐 pb.admins.authRefresh(
body: Map<String, Any?> = emptyMap(),
query: Map<String, Any?> = emptyMap(),
headers: Map<String, String> = emptyMap(),
)
// Sends an admin password reset email.
🔓 pb.admins.requestPasswordReset(
email: String,
body: Map<String, Any?> = emptyMap(),
query: Map<String, Any?> = emptyMap(),
headers: Map<String, String> = emptyMap(),
)
// Confirms an admin password reset request.
🔓 pb.admins.confirmPasswordReset(
passwordResetToken: String,
password: String,
passwordConfirm: String,
body: Map<String, Any?> = emptyMap(),
query: Map<String, Any?> = emptyMap(),
headers: Map<String, String> = emptyMap(),
)
// Returns a paginated admins list.
🔐 pb.admins.getList(
page: Int = 1,
perPage: Int = 30,
skipTotal: Boolean = false,
expand: String? = null,
filter: String? = null,
sort: String? = null,
fields: String? = null,
query: Map<String, Any?> = emptyMap(),
headers: Map<String, String> = emptyMap(),
)
// Returns a list with all admins batch fetched at once.
🔐 pb.admins.getFullList(
batch: Int = 500,
expand: String? = null,
filter: String? = null,
sort: String? = null,
fields: String? = null,
query: Map<String, Any?> = emptyMap(),
headers: Map<String, String> = emptyMap(),
)
// Returns the first found admin matching the specified filter.
🔐 pb.admins.getFirstListItem(
filter: String,
expand: String? = null,
fields: String? = null,
query: Map<String, Any?> = emptyMap(),
headers: Map<String, String> = emptyMap(),
)
// Returns a single admin by their id.
🔐 pb.admins.getOne(
id: String,
expand: String? = null,
fields: String? = null,
query: Map<String, Any?> = emptyMap(),
headers: Map<String, String> = emptyMap(),
)
// Creates a new admin.
🔐 pb.admins.create(
body: T? = null,
expand: String? = null,
fields: String? = null,
query: Map<String, Any?> = emptyMap(),
headers: Map<String, String> = emptyMap(),
files: List<File> = emptyList(),
)
// Updates an existing admin by their id.
🔐 pb.admins.update(
id: String,
body: AdminModel?,
expand: String?,
fields: String?,
query: Map<String, Any?>,
headers: Map<String, String>,
)
// Deletes a single admin by their id.
🔐 pb.admins.delete(
id: String,
body: AdminModel?,
query: Map<String, Any?>,
headers: Map<String, String>,
)
// Returns a map with all available app settings.
🔐 pb.settings.getAll()
// Bulk updates app settings.
🔐 pb.settings.update(settings: Settings): Settings
// Performs a S3 storage connection test.
🔐 pb.settings.testS3(filesystem: String)
// Sends a test email (verification, password-reset, email-change).
🔐 pb.settings.testEmail(
email: String,
template: String,
);
// Generates a new Apple OAuth2 client secret.
🔐 pb.settings.generateAppleClientSecret(body: GenerateAppleClientSecretRequest): String
This service is usually used with custom realtime actions. For records realtime subscriptions you can use the subscribe/unsubscribe methods available in the
pb.collection()
RecordService.
// Initialize the realtime connection (if not already) and register the subscription.
//
// You can subscribe to the `PB_CONNECT` event if you want to listen to the realtime connection connect/reconnect events.
🔓 pb.realtime.subscribe(
topic: String,
listener: SubscriptionFunc,
expand: String? = null,
filter: String? = null,
fields: String? = null,
query: Map<String, Any?> = emptyMap(),
headers: Map<String, String> = emptyMap(),
)
// Unsubscribe from a subscription (if empty - unsubscibe from all registered subscriptions).
🔓 pb.realtime.unsubscribe(topic: String = "")
// Unsubscribe from all subscriptions starting with the provided prefix.
🔓 pb.realtime.unsubscribeByPrefix(topicPrefix: String)
// Checks the health status of the api.
🔓 pb.healthCheck.checkcheck(
query: Map<String, Any?> = emptyMap(),
headers: Map<String, String> = emptyMap(),
)