appwrite-kotlin

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

Appwrite Kotlin SDK

Appwrite Kotlin SDK

Installation

安装

kotlin
// build.gradle.kts — Android
implementation("io.appwrite:sdk-for-android:+")

// build.gradle.kts — Server (Kotlin JVM)
implementation("io.appwrite:sdk-for-kotlin:+")
kotlin
// build.gradle.kts — Android
implementation("io.appwrite:sdk-for-android:+")

// build.gradle.kts — Server (Kotlin JVM)
implementation("io.appwrite:sdk-for-kotlin:+")

Setting Up the Client

客户端设置

Client-side (Android)

客户端(Android)

kotlin
import io.appwrite.Client
import io.appwrite.ID
import io.appwrite.Query
import io.appwrite.enums.OAuthProvider
import io.appwrite.services.Account
import io.appwrite.services.Realtime
import io.appwrite.services.TablesDB
import io.appwrite.services.Storage
import io.appwrite.models.InputFile

val client = Client(context)
    .setEndpoint("https://<REGION>.cloud.appwrite.io/v1")
    .setProject("[PROJECT_ID]")
kotlin
import io.appwrite.Client
import io.appwrite.ID
import io.appwrite.Query
import io.appwrite.enums.OAuthProvider
import io.appwrite.services.Account
import io.appwrite.services.Realtime
import io.appwrite.services.TablesDB
import io.appwrite.services.Storage
import io.appwrite.models.InputFile

val client = Client(context)
    .setEndpoint("https://<REGION>.cloud.appwrite.io/v1")
    .setProject("[PROJECT_ID]")

Server-side (Kotlin JVM)

服务端(Kotlin JVM)

kotlin
import io.appwrite.Client
import io.appwrite.ID
import io.appwrite.Query
import io.appwrite.services.Users
import io.appwrite.services.TablesDB
import io.appwrite.services.Storage
import io.appwrite.services.Functions

val client = Client()
    .setEndpoint("https://<REGION>.cloud.appwrite.io/v1")
    .setProject(System.getenv("APPWRITE_PROJECT_ID"))
    .setKey(System.getenv("APPWRITE_API_KEY"))
kotlin
import io.appwrite.Client
import io.appwrite.ID
import io.appwrite.Query
import io.appwrite.services.Users
import io.appwrite.services.TablesDB
import io.appwrite.services.Storage
import io.appwrite.services.Functions

val client = Client()
    .setEndpoint("https://<REGION>.cloud.appwrite.io/v1")
    .setProject(System.getenv("APPWRITE_PROJECT_ID"))
    .setKey(System.getenv("APPWRITE_API_KEY"))

Code Examples

代码示例

Authentication (client-side)

客户端认证

kotlin
val account = Account(client)

// Signup
account.create(
    userId = ID.unique(),
    email = "user@example.com",
    password = "password123",
    name = "User Name"
)

// Login
val session = account.createEmailPasswordSession(
    email = "user@example.com",
    password = "password123"
)

// OAuth
account.createOAuth2Session(activity = activity, provider = OAuthProvider.GOOGLE)

// Get current user
val user = account.get()

// Logout
account.deleteSession(sessionId = "current")
kotlin
val account = Account(client)

// Signup
account.create(
    userId = ID.unique(),
    email = "user@example.com",
    password = "password123",
    name = "User Name"
)

// Login
val session = account.createEmailPasswordSession(
    email = "user@example.com",
    password = "password123"
)

// OAuth
account.createOAuth2Session(activity = activity, provider = OAuthProvider.GOOGLE)

// Get current user
val user = account.get()

// Logout
account.deleteSession(sessionId = "current")

User Management (server-side)

服务端用户管理

kotlin
val users = Users(client)

// Create user
val user = users.create(
    userId = ID.unique(),
    email = "user@example.com",
    password = "password123",
    name = "User Name"
)

// List users
val list = users.list()

// Get user
val fetched = users.get(userId = "[USER_ID]")

// Delete user
users.delete(userId = "[USER_ID]")
kotlin
val users = Users(client)

// Create user
val user = users.create(
    userId = ID.unique(),
    email = "user@example.com",
    password = "password123",
    name = "User Name"
)

// List users
val list = users.list()

// Get user
val fetched = users.get(userId = "[USER_ID]")

// Delete user
users.delete(userId = "[USER_ID]")

Database Operations

数据库操作

Note: Use
TablesDB
(not the deprecated
Databases
class) for all new code. Only use
Databases
if the existing codebase already relies on it or the user explicitly requests it.
Tip: Prefer named arguments (e.g.,
databaseId = "..."
) for all SDK method calls. Only use positional arguments if the existing codebase already uses them or the user explicitly requests it.
kotlin
val tablesDB = TablesDB(client)

// Create database (server-side only)
val db = tablesDB.create(databaseId = ID.unique(), name = "My Database")

// Create row
val doc = tablesDB.createRow(
    databaseId = "[DATABASE_ID]",
    tableId = "[TABLE_ID]",
    rowId = ID.unique(),
    data = mapOf("title" to "Hello", "done" to false)
)

// Query rows
val results = tablesDB.listRows(
    databaseId = "[DATABASE_ID]",
    tableId = "[TABLE_ID]",
    queries = listOf(Query.equal("done", false), Query.limit(10))
)

// Get row
val row = tablesDB.getRow(databaseId = "[DATABASE_ID]", tableId = "[TABLE_ID]", rowId = "[ROW_ID]")

// Update row
tablesDB.updateRow(
    databaseId = "[DATABASE_ID]",
    tableId = "[TABLE_ID]",
    rowId = "[ROW_ID]",
    data = mapOf("done" to true)
)

// Delete row
tablesDB.deleteRow(
    databaseId = "[DATABASE_ID]",
    tableId = "[TABLE_ID]",
    rowId = "[ROW_ID]"
)
注意: 所有新代码请使用
TablesDB
(而非已弃用的
Databases
类)。仅当现有代码库已依赖
Databases
或用户明确要求时,才使用
Databases
提示: 所有SDK方法调用优先使用命名参数(例如
databaseId = "..."
)。仅当现有代码库已使用位置参数或用户明确要求时,才使用位置参数。
kotlin
val tablesDB = TablesDB(client)

// Create database (server-side only)
val db = tablesDB.create(databaseId = ID.unique(), name = "My Database")

// Create row
val doc = tablesDB.createRow(
    databaseId = "[DATABASE_ID]",
    tableId = "[TABLE_ID]",
    rowId = ID.unique(),
    data = mapOf("title" to "Hello", "done" to false)
)

// Query rows
val results = tablesDB.listRows(
    databaseId = "[DATABASE_ID]",
    tableId = "[TABLE_ID]",
    queries = listOf(Query.equal("done", false), Query.limit(10))
)

// Get row
val row = tablesDB.getRow(databaseId = "[DATABASE_ID]", tableId = "[TABLE_ID]", rowId = "[ROW_ID]")

// Update row
tablesDB.updateRow(
    databaseId = "[DATABASE_ID]",
    tableId = "[TABLE_ID]",
    rowId = "[ROW_ID]",
    data = mapOf("done" to true)
)

// Delete row
tablesDB.deleteRow(
    databaseId = "[DATABASE_ID]",
    tableId = "[TABLE_ID]",
    rowId = "[ROW_ID]"
)

String Column Types

字符串列类型

Note: The legacy
string
type is deprecated. Use explicit column types for all new columns.
TypeMax charactersIndexingStorage
varchar
16,383Full index (if size ≤ 768)Inline in row
text
16,383Prefix onlyOff-page
mediumtext
4,194,303Prefix onlyOff-page
longtext
1,073,741,823Prefix onlyOff-page
  • varchar
    is stored inline and counts towards the 64 KB row size limit. Prefer for short, indexed fields like names, slugs, or identifiers.
  • text
    ,
    mediumtext
    , and
    longtext
    are stored off-page (only a 20-byte pointer lives in the row), so they don't consume the row size budget.
    size
    is not required for these types.
kotlin
// Create table with explicit string column types
tablesDB.createTable(
    databaseId = "[DATABASE_ID]",
    tableId = ID.unique(),
    name = "articles",
    columns = listOf(
        mapOf("key" to "title",    "type" to "varchar",    "size" to 255, "required" to true),
        mapOf("key" to "summary",  "type" to "text",                      "required" to false),
        mapOf("key" to "body",     "type" to "mediumtext",                "required" to false),
        mapOf("key" to "raw_data", "type" to "longtext",                  "required" to false),
    )
)
注意: 旧版
string
类型已弃用。所有新列请使用明确的列类型。
类型最大字符数索引存储方式
varchar
16,383全索引(若长度≤768)行内存储
text
16,383仅前缀索引页外存储
mediumtext
4,194,303仅前缀索引页外存储
longtext
1,073,741,823仅前缀索引页外存储
  • varchar
    存储在行内,计入64 KB的行大小限制。适合短文本、需要索引的字段,如名称、别名或标识符。
  • text
    mediumtext
    longtext
    存储在页外(行中仅保留20字节的指针),因此不占用行大小配额。这些类型不需要指定
    size
kotlin
// Create table with explicit string column types
tablesDB.createTable(
    databaseId = "[DATABASE_ID]",
    tableId = ID.unique(),
    name = "articles",
    columns = listOf(
        mapOf("key" to "title",    "type" to "varchar",    "size" to 255, "required" to true),
        mapOf("key" to "summary",  "type" to "text",                      "required" to false),
        mapOf("key" to "body",     "type" to "mediumtext",                "required" to false),
        mapOf("key" to "raw_data", "type" to "longtext",                  "required" to false),
    )
)

Query Methods

查询方法

kotlin
// Filtering
Query.equal("field", "value")             // == (or pass list for IN)
Query.notEqual("field", "value")          // !=
Query.lessThan("field", 100)              // <
Query.lessThanEqual("field", 100)         // <=
Query.greaterThan("field", 100)           // >
Query.greaterThanEqual("field", 100)      // >=
Query.between("field", 1, 100)            // 1 <= field <= 100
Query.isNull("field")                     // is null
Query.isNotNull("field")                  // is not null
Query.startsWith("field", "prefix")       // starts with
Query.endsWith("field", "suffix")         // ends with
Query.contains("field", "sub")            // contains
Query.search("field", "keywords")         // full-text search (requires index)

// Sorting
Query.orderAsc("field")
Query.orderDesc("field")

// Pagination
Query.limit(25)                           // max rows (default 25, max 100)
Query.offset(0)                           // skip N rows
Query.cursorAfter("[ROW_ID]")             // cursor pagination (preferred)
Query.cursorBefore("[ROW_ID]")

// Selection & Logic
Query.select(listOf("field1", "field2"))
Query.or(listOf(Query.equal("a", 1), Query.equal("b", 2)))   // OR
Query.and(listOf(Query.greaterThan("age", 18), Query.lessThan("age", 65)))  // AND (default)
kotlin
// Filtering
Query.equal("field", "value")             // == (or pass list for IN)
Query.notEqual("field", "value")          // !=
Query.lessThan("field", 100)              // <
Query.lessThanEqual("field", 100)         // <=
Query.greaterThan("field", 100)           // >
Query.greaterThanEqual("field", 100)      // >=
Query.between("field", 1, 100)            // 1 <= field <= 100
Query.isNull("field")                     // is null
Query.isNotNull("field")                  // is not null
Query.startsWith("field", "prefix")       // starts with
Query.endsWith("field", "suffix")         // ends with
Query.contains("field", "sub")            // contains
Query.search("field", "keywords")         // full-text search (requires index)

// Sorting
Query.orderAsc("field")
Query.orderDesc("field")

// Pagination
Query.limit(25)                           // max rows (default 25, max 100)
Query.offset(0)                           // skip N rows
Query.cursorAfter("[ROW_ID]")             // cursor pagination (preferred)
Query.cursorBefore("[ROW_ID]")

// Selection & Logic
Query.select(listOf("field1", "field2"))
Query.or(listOf(Query.equal("a", 1), Query.equal("b", 2)))   // OR
Query.and(listOf(Query.greaterThan("age", 18), Query.lessThan("age", 65)))  // AND (default)

File Storage

文件存储

kotlin
val storage = Storage(client)

// Upload file
val file = storage.createFile(
    bucketId = "[BUCKET_ID]",
    fileId = ID.unique(),
    file = InputFile.fromPath("/path/to/file.png")
)

// Get file preview
val preview = storage.getFilePreview(
    bucketId = "[BUCKET_ID]",
    fileId = "[FILE_ID]",
    width = 300,
    height = 300
)

// List files
val files = storage.listFiles(bucketId = "[BUCKET_ID]")

// Delete file
storage.deleteFile(bucketId = "[BUCKET_ID]", fileId = "[FILE_ID]")
kotlin
val storage = Storage(client)

// Upload file
val file = storage.createFile(
    bucketId = "[BUCKET_ID]",
    fileId = ID.unique(),
    file = InputFile.fromPath("/path/to/file.png")
)

// Get file preview
val preview = storage.getFilePreview(
    bucketId = "[BUCKET_ID]",
    fileId = "[FILE_ID]",
    width = 300,
    height = 300
)

// List files
val files = storage.listFiles(bucketId = "[BUCKET_ID]")

// Delete file
storage.deleteFile(bucketId = "[BUCKET_ID]", fileId = "[FILE_ID]")

InputFile Factory Methods

InputFile 工厂方法

kotlin
import io.appwrite.models.InputFile

InputFile.fromPath("/path/to/file.png")              // from filesystem path
InputFile.fromBytes(byteArray, "file.png")           // from ByteArray
kotlin
import io.appwrite.models.InputFile

InputFile.fromPath("/path/to/file.png")              // from filesystem path
InputFile.fromBytes(byteArray, "file.png")           // from ByteArray

Teams

团队管理

kotlin
val teams = Teams(client)

// Create team
val team = teams.create(teamId = ID.unique(), name = "Engineering")

// List teams
val list = teams.list()

// Create membership (invite user by email)
val membership = teams.createMembership(
    teamId = "[TEAM_ID]",
    roles = listOf("editor"),
    email = "user@example.com"
)

// List memberships
val members = teams.listMemberships(teamId = "[TEAM_ID]")

// Update membership roles
teams.updateMembership(teamId = "[TEAM_ID]", membershipId = "[MEMBERSHIP_ID]", roles = listOf("admin"))

// Delete team
teams.delete(teamId = "[TEAM_ID]")
Role-based access: Use
Role.team("[TEAM_ID]")
for all team members or
Role.team("[TEAM_ID]", "editor")
for a specific team role when setting permissions.
kotlin
val teams = Teams(client)

// Create team
val team = teams.create(teamId = ID.unique(), name = "Engineering")

// List teams
val list = teams.list()

// Create membership (invite user by email)
val membership = teams.createMembership(
    teamId = "[TEAM_ID]",
    roles = listOf("editor"),
    email = "user@example.com"
)

// List memberships
val members = teams.listMemberships(teamId = "[TEAM_ID]")

// Update membership roles
teams.updateMembership(teamId = "[TEAM_ID]", membershipId = "[MEMBERSHIP_ID]", roles = listOf("admin"))

// Delete team
teams.delete(teamId = "[TEAM_ID]")
基于角色的访问控制: 设置权限时,使用
Role.team("[TEAM_ID]")
表示所有团队成员,或使用
Role.team("[TEAM_ID]", "editor")
表示特定团队角色。

Real-time Subscriptions (client-side)

实时订阅(客户端)

kotlin
import io.appwrite.Channel

val realtime = Realtime(client)

// Subscribe to row changes
val subscription = realtime.subscribe(
    Channel.tablesdb("[DATABASE_ID]").table("[TABLE_ID]").row()
) { response ->
    println(response.events)   // e.g. ["tablesdb.*.tables.*.rows.*.create"]
    println(response.payload)  // the affected resource
}

// Subscribe to multiple channels
val multi = realtime.subscribe(
    Channel.tablesdb("[DATABASE_ID]").table("[TABLE_ID]").row(),
    Channel.bucket("[BUCKET_ID]").file()
) { response -> /* ... */ }

// Cleanup
subscription.close()
Available channels:
ChannelDescription
account
Changes to the authenticated user's account
tablesdb.[DB_ID].tables.[TABLE_ID].rows
All rows in a table
tablesdb.[DB_ID].tables.[TABLE_ID].rows.[ROW_ID]
A specific row
buckets.[BUCKET_ID].files
All files in a bucket
buckets.[BUCKET_ID].files.[FILE_ID]
A specific file
teams
Changes to teams the user belongs to
teams.[TEAM_ID]
A specific team
memberships
The user's team memberships
functions.[FUNCTION_ID].executions
Function execution updates
Response fields:
events
(array),
payload
(resource),
channels
(matched),
timestamp
(ISO 8601).
kotlin
import io.appwrite.Channel

val realtime = Realtime(client)

// Subscribe to row changes
val subscription = realtime.subscribe(
    Channel.tablesdb("[DATABASE_ID]").table("[TABLE_ID]").row()
) { response ->
    println(response.events)   // e.g. ["tablesdb.*.tables.*.rows.*.create"]
    println(response.payload)  // the affected resource
}

// Subscribe to multiple channels
val multi = realtime.subscribe(
    Channel.tablesdb("[DATABASE_ID]").table("[TABLE_ID]").row(),
    Channel.bucket("[BUCKET_ID]").file()
) { response -> /* ... */ }

// Cleanup
subscription.close()
可用频道:
频道描述
account
已认证用户的账户变更
tablesdb.[DB_ID].tables.[TABLE_ID].rows
表中所有行的变更
tablesdb.[DB_ID].tables.[TABLE_ID].rows.[ROW_ID]
特定行的变更
buckets.[BUCKET_ID].files
存储桶中所有文件的变更
buckets.[BUCKET_ID].files.[FILE_ID]
特定文件的变更
teams
用户所属团队的变更
teams.[TEAM_ID]
特定团队的变更
memberships
用户的团队成员身份变更
functions.[FUNCTION_ID].executions
函数执行状态更新
响应字段:
events
(数组)、
payload
(资源内容)、
channels
(匹配的频道)、
timestamp
(ISO 8601格式时间戳)。

Serverless Functions (server-side)

无服务器函数(服务端)

kotlin
val functions = Functions(client)

// Execute function
val execution = functions.createExecution(
    functionId = "[FUNCTION_ID]",
    body = """{"key": "value"}"""
)

// List executions
val executions = functions.listExecutions(functionId = "[FUNCTION_ID]")
kotlin
val functions = Functions(client)

// Execute function
val execution = functions.createExecution(
    functionId = "[FUNCTION_ID]",
    body = """{"key": "value"}"""
)

// List executions
val executions = functions.listExecutions(functionId = "[FUNCTION_ID]")

Writing a Function Handler (Kotlin runtime)

编写函数处理器(Kotlin 运行时)

kotlin
// src/Main.kt — Appwrite Function entry point
import io.openruntimes.kotlin.RuntimeContext
import io.openruntimes.kotlin.RuntimeOutput

fun main(context: RuntimeContext): RuntimeOutput {
    // context.req.body        — raw body (String)
    // context.req.bodyJson    — parsed JSON (Map)
    // context.req.headers     — headers (Map)
    // context.req.method      — HTTP method
    // context.req.path        — URL path
    // context.req.query       — query params (Map)

    context.log("Processing: ${context.req.method} ${context.req.path}")

    if (context.req.method == "GET") {
        return context.res.json(mapOf("message" to "Hello from Appwrite Function!"))
    }

    return context.res.json(mapOf("success" to true))    // JSON
    // context.res.text("Hello")                          // plain text
    // context.res.empty()                                // 204
    // context.res.redirect("https://...")                 // 302
}
kotlin
// src/Main.kt — Appwrite Function entry point
import io.openruntimes.kotlin.RuntimeContext
import io.openruntimes.kotlin.RuntimeOutput

fun main(context: RuntimeContext): RuntimeOutput {
    // context.req.body        — raw body (String)
    // context.req.bodyJson    — parsed JSON (Map)
    // context.req.headers     — headers (Map)
    // context.req.method      — HTTP method
    // context.req.path        — URL path
    // context.req.query       — query params (Map)

    context.log("Processing: ${context.req.method} ${context.req.path}")

    if (context.req.method == "GET") {
        return context.res.json(mapOf("message" to "Hello from Appwrite Function!"))
    }

    return context.res.json(mapOf("success" to true))    // JSON
    // context.res.text("Hello")                          // plain text
    // context.res.empty()                                // 204
    // context.res.redirect("https://...")                 // 302
}

Server-Side Rendering (SSR) Authentication

服务端渲染(SSR)认证

SSR apps using Kotlin server frameworks (Ktor, Spring Boot, etc.) use the server SDK to handle auth. You need two clients:
  • Admin client — uses an API key, creates sessions, bypasses rate limits (reusable singleton)
  • Session client — uses a session cookie, acts on behalf of a user (create per-request, never share)
kotlin
import io.appwrite.Client
import io.appwrite.services.Account
import io.appwrite.enums.OAuthProvider

// Admin client (reusable)
val adminClient = Client()
    .setEndpoint("https://<REGION>.cloud.appwrite.io/v1")
    .setProject("[PROJECT_ID]")
    .setKey(System.getenv("APPWRITE_API_KEY"))

// Session client (create per-request)
val sessionClient = Client()
    .setEndpoint("https://<REGION>.cloud.appwrite.io/v1")
    .setProject("[PROJECT_ID]")

val session = call.request.cookies["a_session_[PROJECT_ID]"]
if (session != null) {
    sessionClient.setSession(session)
}
使用Kotlin服务端框架(Ktor、Spring Boot等)的SSR应用,需使用服务端SDK处理认证。您需要两个客户端:
  • 管理员客户端 — 使用API密钥,可创建会话、绕过速率限制(可复用单例)
  • 会话客户端 — 使用会话Cookie,代表用户执行操作(每个请求创建一个,切勿共享)
kotlin
import io.appwrite.Client
import io.appwrite.services.Account
import io.appwrite.enums.OAuthProvider

// Admin client (reusable)
val adminClient = Client()
    .setEndpoint("https://<REGION>.cloud.appwrite.io/v1")
    .setProject("[PROJECT_ID]")
    .setKey(System.getenv("APPWRITE_API_KEY"))

// Session client (create per-request)
val sessionClient = Client()
    .setEndpoint("https://<REGION>.cloud.appwrite.io/v1")
    .setProject("[PROJECT_ID]")

val session = call.request.cookies["a_session_[PROJECT_ID]"]
if (session != null) {
    sessionClient.setSession(session)
}

Email/Password Login (Ktor)

邮箱/密码登录(Ktor)

kotlin
post("/login") {
    val body = call.receive<LoginRequest>()
    val account = Account(adminClient)
    val session = account.createEmailPasswordSession(
        email = body.email,
        password = body.password,
    )

    // Cookie name must be a_session_<PROJECT_ID>
    call.response.cookies.append(Cookie(
        name = "a_session_[PROJECT_ID]",
        value = session.secret,
        httpOnly = true,
        secure = true,
        extensions = mapOf("SameSite" to "Strict"),
        path = "/",
    ))
    call.respond(mapOf("success" to true))
}
kotlin
post("/login") {
    val body = call.receive<LoginRequest>()
    val account = Account(adminClient)
    val session = account.createEmailPasswordSession(
        email = body.email,
        password = body.password,
    )

    // Cookie name must be a_session_<PROJECT_ID>
    call.response.cookies.append(Cookie(
        name = "a_session_[PROJECT_ID]",
        value = session.secret,
        httpOnly = true,
        secure = true,
        extensions = mapOf("SameSite" to "Strict"),
        path = "/",
    ))
    call.respond(mapOf("success" to true))
}

Authenticated Requests

已认证请求处理

kotlin
get("/user") {
    val session = call.request.cookies["a_session_[PROJECT_ID]"]
        ?: return@get call.respond(HttpStatusCode.Unauthorized)

    val sessionClient = Client()
        .setEndpoint("https://<REGION>.cloud.appwrite.io/v1")
        .setProject("[PROJECT_ID]")
        .setSession(session)

    val account = Account(sessionClient)
    val user = account.get()
    call.respond(user)
}
kotlin
get("/user") {
    val session = call.request.cookies["a_session_[PROJECT_ID]"]
        ?: return@get call.respond(HttpStatusCode.Unauthorized)

    val sessionClient = Client()
        .setEndpoint("https://<REGION>.cloud.appwrite.io/v1")
        .setProject("[PROJECT_ID]")
        .setSession(session)

    val account = Account(sessionClient)
    val user = account.get()
    call.respond(user)
}

OAuth2 SSR Flow

OAuth2 SSR 流程

kotlin
// Step 1: Redirect to OAuth provider
get("/oauth") {
    val account = Account(adminClient)
    val redirectUrl = account.createOAuth2Token(
        provider = OAuthProvider.GITHUB,
        success = "https://example.com/oauth/success",
        failure = "https://example.com/oauth/failure",
    )
    call.respondRedirect(redirectUrl)
}

// Step 2: Handle callback — exchange token for session
get("/oauth/success") {
    val account = Account(adminClient)
    val session = account.createSession(
        userId = call.parameters["userId"]!!,
        secret = call.parameters["secret"]!!,
    )

    call.response.cookies.append(Cookie(
        name = "a_session_[PROJECT_ID]", value = session.secret,
        httpOnly = true, secure = true,
        extensions = mapOf("SameSite" to "Strict"), path = "/",
    ))
    call.respond(mapOf("success" to true))
}
Cookie security: Always use
httpOnly
,
secure
, and
SameSite=Strict
to prevent XSS. The cookie name must be
a_session_<PROJECT_ID>
.
Forwarding user agent: Call
sessionClient.setForwardedUserAgent(call.request.headers["User-Agent"])
to record the end-user's browser info for debugging and security.
kotlin
// Step 1: Redirect to OAuth provider
get("/oauth") {
    val account = Account(adminClient)
    val redirectUrl = account.createOAuth2Token(
        provider = OAuthProvider.GITHUB,
        success = "https://example.com/oauth/success",
        failure = "https://example.com/oauth/failure",
    )
    call.respondRedirect(redirectUrl)
}

// Step 2: Handle callback — exchange token for session
get("/oauth/success") {
    val account = Account(adminClient)
    val session = account.createSession(
        userId = call.parameters["userId"]!!,
        secret = call.parameters["secret"]!!,
    )

    call.response.cookies.append(Cookie(
        name = "a_session_[PROJECT_ID]", value = session.secret,
        httpOnly = true, secure = true,
        extensions = mapOf("SameSite" to "Strict"), path = "/",
    ))
    call.respond(mapOf("success" to true))
}
Cookie 安全: 始终使用
httpOnly
secure
SameSite=Strict
来防止XSS攻击。Cookie名称必须为
a_session_<PROJECT_ID>
转发用户代理: 调用
sessionClient.setForwardedUserAgent(call.request.headers["User-Agent"])
来记录终端用户的浏览器信息,用于调试和安全审计。

Error Handling

错误处理

kotlin
import io.appwrite.AppwriteException

try {
    val row = tablesDB.getRow(databaseId = "[DATABASE_ID]", tableId = "[TABLE_ID]", rowId = "[ROW_ID]")
} catch (e: AppwriteException) {
    println(e.message)     // human-readable message
    println(e.code)        // HTTP status code (Int)
    println(e.type)        // error type (e.g. "document_not_found")
    println(e.response)    // full response body (Map)
}
Common error codes:
CodeMeaning
401
Unauthorized — missing or invalid session/API key
403
Forbidden — insufficient permissions
404
Not found — resource does not exist
409
Conflict — duplicate ID or unique constraint
429
Rate limited — too many requests
kotlin
import io.appwrite.AppwriteException

try {
    val row = tablesDB.getRow(databaseId = "[DATABASE_ID]", tableId = "[TABLE_ID]", rowId = "[ROW_ID]")
} catch (e: AppwriteException) {
    println(e.message)     // human-readable message
    println(e.code)        // HTTP status code (Int)
    println(e.type)        // error type (e.g. "document_not_found")
    println(e.response)    // full response body (Map)
}
常见错误码:
代码含义
401
未授权 — 缺少或无效的会话/API密钥
403
禁止访问 — 权限不足
404
未找到 — 资源不存在
409
冲突 — 重复ID或唯一约束冲突
429
请求受限 — 请求过于频繁

Permissions & Roles (Critical)

权限与角色(重点)

Appwrite uses permission strings to control access to resources. Each permission pairs an action (
read
,
update
,
delete
,
create
, or
write
which grants create + update + delete) with a role target. By default, no user has access unless permissions are explicitly set at the document/file level or inherited from the collection/bucket settings. Permissions are arrays of strings built with the
Permission
and
Role
helpers.
kotlin
import io.appwrite.Permission
import io.appwrite.Role
Appwrite使用权限字符串控制资源访问。每个权限将操作(
read
update
delete
create
,或代表create+update+delete的
write
)与角色目标配对。默认情况下,所有用户均无访问权限,除非在文档/文件级别显式设置权限,或从集合/存储桶设置继承权限。权限是使用
Permission
Role
助手构建的字符串数组。
kotlin
import io.appwrite.Permission
import io.appwrite.Role

Database Row with Permissions

带权限的数据库行

kotlin
val doc = tablesDB.createRow(
    databaseId = "[DATABASE_ID]",
    tableId = "[TABLE_ID]",
    rowId = ID.unique(),
    data = mapOf("title" to "Hello World"),
    permissions = listOf(
        Permission.read(Role.user("[USER_ID]")),     // specific user can read
        Permission.update(Role.user("[USER_ID]")),   // specific user can update
        Permission.read(Role.team("[TEAM_ID]")),     // all team members can read
        Permission.read(Role.any()),                 // anyone (including guests) can read
    )
)
kotlin
val doc = tablesDB.createRow(
    databaseId = "[DATABASE_ID]",
    tableId = "[TABLE_ID]",
    rowId = ID.unique(),
    data = mapOf("title" to "Hello World"),
    permissions = listOf(
        Permission.read(Role.user("[USER_ID]")),     // 特定用户可读取
        Permission.update(Role.user("[USER_ID]")),   // 特定用户可更新
        Permission.read(Role.team("[TEAM_ID]")),     // 所有团队成员可读取
        Permission.read(Role.any()),                 // 任何人(包括访客)可读取
    )
)

File Upload with Permissions

带权限的文件上传

kotlin
val file = storage.createFile(
    bucketId = "[BUCKET_ID]",
    fileId = ID.unique(),
    file = InputFile.fromPath("/path/to/file.png"),
    permissions = listOf(
        Permission.read(Role.any()),
        Permission.update(Role.user("[USER_ID]")),
        Permission.delete(Role.user("[USER_ID]")),
    )
)
When to set permissions: Set document/file-level permissions when you need per-resource access control. If all documents in a collection share the same rules, configure permissions at the collection/bucket level and leave document permissions empty.
Common mistakes:
  • Forgetting permissions — the resource becomes inaccessible to all users (including the creator)
  • Role.any()
    with
    write
    /
    update
    /
    delete
    — allows any user, including unauthenticated guests, to modify or remove the resource
  • Permission.read(Role.any())
    on sensitive data
    — makes the resource publicly readable
kotlin
val file = storage.createFile(
    bucketId = "[BUCKET_ID]",
    fileId = ID.unique(),
    file = InputFile.fromPath("/path/to/file.png"),
    permissions = listOf(
        Permission.read(Role.any()),
        Permission.update(Role.user("[USER_ID]")),
        Permission.delete(Role.user("[USER_ID]")),
    )
)
何时设置权限: 当需要按资源粒度控制访问时,设置文档/文件级权限。如果集合中的所有文档共享相同规则,请在集合/存储桶级别配置权限,文档权限留空即可。
常见错误:
  • 忘记设置权限 — 资源对所有用户(包括创建者)均不可访问
  • Role.any()
    write
    /
    update
    /
    delete
    配合使用
    — 允许任何用户(包括未认证访客)修改或删除资源
  • 敏感数据使用
    Permission.read(Role.any())
    — 使资源可公开读取