appwrite-swift

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

Appwrite Swift SDK

Appwrite Swift SDK

Installation

安装

swift
// Swift Package Manager — Package.swift
.package(url: "https://github.com/appwrite/sdk-for-swift", branch: "main")
swift
// Swift Package Manager — Package.swift
.package(url: "https://github.com/appwrite/sdk-for-swift", branch: "main")

Setting Up the Client

客户端设置

Client-side (Apple platforms)

Apple 平台客户端

swift
import Appwrite

let client = Client()
    .setEndpoint("https://<REGION>.cloud.appwrite.io/v1")
    .setProject("[PROJECT_ID]")
swift
import Appwrite

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

Server-side (Swift)

服务端 Swift

swift
import Appwrite

let client = Client()
    .setEndpoint("https://<REGION>.cloud.appwrite.io/v1")
    .setProject(ProcessInfo.processInfo.environment["APPWRITE_PROJECT_ID"]!)
    .setKey(ProcessInfo.processInfo.environment["APPWRITE_API_KEY"]!)
swift
import Appwrite

let client = Client()
    .setEndpoint("https://<REGION>.cloud.appwrite.io/v1")
    .setProject(ProcessInfo.processInfo.environment["APPWRITE_PROJECT_ID"]!)
    .setKey(ProcessInfo.processInfo.environment["APPWRITE_API_KEY"]!)

Code Examples

代码示例

Authentication (client-side)

客户端认证

swift
let account = Account(client)

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

// Login
let session = try await account.createEmailPasswordSession(email: "user@example.com", password: "password123")

// OAuth
try await account.createOAuth2Session(provider: .google)

// Get current user
let me = try await account.get()

// Logout
try await account.deleteSession(sessionId: "current")
swift
let account = Account(client)

// 注册
let user = try await account.create(userId: ID.unique(), email: "user@example.com", password: "password123", name: "User Name")

// 登录
let session = try await account.createEmailPasswordSession(email: "user@example.com", password: "password123")

// OAuth 登录
try await account.createOAuth2Session(provider: .google)

// 获取当前用户
let me = try await account.get()

// 登出
try await account.deleteSession(sessionId: "current")

User Management (server-side)

服务端用户管理

swift
let users = Users(client)

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

// List users
let list = try await users.list(queries: [Query.limit(25)])

// Get user
let fetched = try await users.get(userId: "[USER_ID]")

// Delete user
try await users.delete(userId: "[USER_ID]")
swift
let users = Users(client)

// 创建用户
let user = try await users.create(userId: ID.unique(), email: "user@example.com", password: "password123", name: "User Name")

// 列出用户
let list = try await users.list(queries: [Query.limit(25)])

// 获取用户信息
let fetched = try await users.get(userId: "[USER_ID]")

// 删除用户
try await 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 parameters (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.
swift
let tablesDB = TablesDB(client)

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

// Create row
let doc = try await tablesDB.createRow(databaseId: "[DATABASE_ID]", tableId: "[TABLE_ID]", rowId: ID.unique(), data: [
    "title": "Hello",
    "done": false
])

// Query rows
let results = try await tablesDB.listRows(databaseId: "[DATABASE_ID]", tableId: "[TABLE_ID]", queries: [
    Query.equal("done", value: false),
    Query.limit(10)
])

// Get row
let row = try await tablesDB.getRow(databaseId: "[DATABASE_ID]", tableId: "[TABLE_ID]", rowId: "[ROW_ID]")

// Update row
try await tablesDB.updateRow(databaseId: "[DATABASE_ID]", tableId: "[TABLE_ID]", rowId: "[ROW_ID]", data: ["done": true])

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

// 创建数据库(仅服务端可用)
let db = try await tablesDB.create(databaseId: ID.unique(), name: "My Database")

// 创建行
let doc = try await tablesDB.createRow(databaseId: "[DATABASE_ID]", tableId: "[TABLE_ID]", rowId: ID.unique(), data: [
    "title": "Hello",
    "done": false
])

// 查询行
let results = try await tablesDB.listRows(databaseId: "[DATABASE_ID]", tableId: "[TABLE_ID]", queries: [
    Query.equal("done", value: false),
    Query.limit(10)
])

// 获取行
let row = try await tablesDB.getRow(databaseId: "[DATABASE_ID]", tableId: "[TABLE_ID]", rowId: "[ROW_ID]")

// 更新行
try await tablesDB.updateRow(databaseId: "[DATABASE_ID]", tableId: "[TABLE_ID]", rowId: "[ROW_ID]", data: ["done": true])

// 删除行
try await 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.
swift
// Create table with explicit string column types
try await tablesDB.createTable(
    databaseId: "[DATABASE_ID]",
    tableId: ID.unique(),
    name: "articles",
    columns: [
        ["key": "title",    "type": "varchar",    "size": 255, "required": true],
        ["key": "summary",  "type": "text",                    "required": false],
        ["key": "body",     "type": "mediumtext",              "required": false],
        ["key": "raw_data", "type": "longtext",                "required": 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
swift
// 创建包含明确字符串列类型的表
try await tablesDB.createTable(
    databaseId: "[DATABASE_ID]",
    tableId: ID.unique(),
    name: "articles",
    columns: [
        ["key": "title",    "type": "varchar",    "size": 255, "required": true],
        ["key": "summary",  "type": "text",                    "required": false],
        ["key": "body",     "type": "mediumtext",              "required": false],
        ["key": "raw_data", "type": "longtext",                "required": false],
    ]
)

Query Methods

查询方法

swift
// Filtering
Query.equal("field", value: "value")          // == (or pass array for IN)
Query.notEqual("field", value: "value")       // !=
Query.lessThan("field", value: 100)           // <
Query.lessThanEqual("field", value: 100)      // <=
Query.greaterThan("field", value: 100)        // >
Query.greaterThanEqual("field", value: 100)   // >=
Query.between("field", start: 1, end: 100)    // 1 <= field <= 100
Query.isNull("field")                         // is null
Query.isNotNull("field")                      // is not null
Query.startsWith("field", value: "prefix")    // starts with
Query.endsWith("field", value: "suffix")      // ends with
Query.contains("field", value: "sub")         // contains
Query.search("field", value: "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(["field1", "field2"])
Query.or([Query.equal("a", value: 1), Query.equal("b", value: 2)])   // OR
Query.and([Query.greaterThan("age", value: 18), Query.lessThan("age", value: 65)])  // AND (default)
swift
// 过滤
Query.equal("field", value: "value")          // ==(传入数组可实现 IN 查询)
Query.notEqual("field", value: "value")       // !=
Query.lessThan("field", value: 100)           // <
Query.lessThanEqual("field", value: 100)      // <=
Query.greaterThan("field", value: 100)        // >
Query.greaterThanEqual("field", value: 100)   // >=
Query.between("field", start: 1, end: 100)    // 1 <= 字段 <= 100
Query.isNull("field")                         // 为空
Query.isNotNull("field")                      // 不为空
Query.startsWith("field", value: "prefix")    // 以指定前缀开头
Query.endsWith("field", value: "suffix")      // 以指定后缀结尾
Query.contains("field", value: "sub")         // 包含指定子串
Query.search("field", value: "keywords")      // 全文搜索(需要提前创建索引)

// 排序
Query.orderAsc("field")
Query.orderDesc("field")

// 分页
Query.limit(25)                               // 最大行数(默认25,最大100)
Query.offset(0)                               // 跳过N行
Query.cursorAfter("[ROW_ID]")                 // 游标分页(推荐使用)
Query.cursorBefore("[ROW_ID]")

// 字段选择与逻辑组合
Query.select(["field1", "field2"])
Query.or([Query.equal("a", value: 1), Query.equal("b", value: 2)])   // 或逻辑
Query.and([Query.greaterThan("age", value: 18), Query.lessThan("age", value: 65)])  // 与逻辑(默认)

File Storage

文件存储

swift
let storage = Storage(client)

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

// List files
let files = try await storage.listFiles(bucketId: "[BUCKET_ID]")

// Delete file
try await storage.deleteFile(bucketId: "[BUCKET_ID]", fileId: "[FILE_ID]")
swift
let storage = Storage(client)

// 上传文件
let file = try await storage.createFile(bucketId: "[BUCKET_ID]", fileId: ID.unique(), file: InputFile.fromPath("/path/to/file.png"))

// 列出文件
let files = try await storage.listFiles(bucketId: "[BUCKET_ID]")

// 删除文件
try await storage.deleteFile(bucketId: "[BUCKET_ID]", fileId: "[FILE_ID]")

InputFile Factory Methods

InputFile 工厂方法

swift
InputFile.fromPath("/path/to/file.png")                    // from filesystem path
InputFile.fromData(data, filename: "file.png", mimeType: "image/png")  // from Data
swift
InputFile.fromPath("/path/to/file.png")                    // 从文件系统路径创建
InputFile.fromData(data, filename: "file.png", mimeType: "image/png")  // 从 Data 对象创建

Teams

团队管理

swift
let teams = Teams(client)

// Create team
let team = try await teams.create(teamId: ID.unique(), name: "Engineering")

// List teams
let list = try await teams.list()

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

// List memberships
let members = try await teams.listMemberships(teamId: "[TEAM_ID]")

// Update membership roles
try await teams.updateMembership(teamId: "[TEAM_ID]", membershipId: "[MEMBERSHIP_ID]", roles: ["admin"])

// Delete team
try await 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.
swift
let teams = Teams(client)

// 创建团队
let team = try await teams.create(teamId: ID.unique(), name: "Engineering")

// 列出团队
let list = try await teams.list()

// 创建团队成员(通过邮箱邀请用户)
let membership = try await teams.createMembership(
    teamId: "[TEAM_ID]",
    roles: ["editor"],
    email: "user@example.com"
)

// 列出团队成员
let members = try await teams.listMemberships(teamId: "[TEAM_ID]")

// 更新团队成员角色
try await teams.updateMembership(teamId: "[TEAM_ID]", membershipId: "[MEMBERSHIP_ID]", roles: ["admin"])

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

Real-time Subscriptions (client-side)

实时订阅(客户端)

swift
let realtime = Realtime(client)

let subscription = realtime.subscribe(channels: ["databases.[DATABASE_ID].tables.[TABLE_ID].rows"]) { response in
    print(response.events)   // e.g. ["databases.*.tables.*.rows.*.create"]
    print(response.payload)  // the affected resource
}

// Subscribe to multiple channels
let multi = realtime.subscribe(channels: [
    "databases.[DATABASE_ID].tables.[TABLE_ID].rows",
    "buckets.[BUCKET_ID].files",
]) { response in /* ... */ }

// Cleanup
subscription.close()
Available channels:
ChannelDescription
account
Changes to the authenticated user's account
databases.[DB_ID].tables.[TABLE_ID].rows
All rows in a table
databases.[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).
swift
let realtime = Realtime(client)

let subscription = realtime.subscribe(channels: ["databases.[DATABASE_ID].tables.[TABLE_ID].rows"]) { response in
    print(response.events)   // 示例:["databases.*.tables.*.rows.*.create"]
    print(response.payload)  // 受影响的资源数据
}

// 订阅多个频道
let multi = realtime.subscribe(channels: [
    "databases.[DATABASE_ID].tables.[TABLE_ID].rows",
    "buckets.[BUCKET_ID].files",
]) { response in /* ... */ }

// 取消订阅
subscription.close()
可用频道:
频道描述
account
已认证用户的账户变更
databases.[DB_ID].tables.[TABLE_ID].rows
表中所有行的变更
databases.[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)

无服务器函数(服务端)

swift
let functions = Functions(client)

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

// List executions
let executions = try await functions.listExecutions(functionId: "[FUNCTION_ID]")
swift
let functions = Functions(client)

// 执行函数
let execution = try await functions.createExecution(functionId: "[FUNCTION_ID]", body: "{\"key\": \"value\"}")

// 列出函数执行记录
let executions = try await functions.listExecutions(functionId: "[FUNCTION_ID]")

Writing a Function Handler (Swift runtime)

Swift 运行时函数处理器编写

swift
// Sources/main.swift — Appwrite Function entry point
func main(context: RuntimeContext) async throws -> RuntimeOutput {
    // context.req.body        — raw body (String)
    // context.req.bodyJson    — parsed JSON ([String: Any]?)
    // context.req.headers     — headers ([String: String])
    // context.req.method      — HTTP method
    // context.req.path        — URL path
    // context.req.query       — query params ([String: String])

    context.log("Processing: \(context.req.method) \(context.req.path)")

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

    return context.res.json(["success": true])       // JSON
    // context.res.text("Hello")                     // plain text
    // context.res.empty()                           // 204
    // context.res.redirect("https://...")            // 302
}
swift
// Sources/main.swift — Appwrite 函数入口
func main(context: RuntimeContext) async throws -> RuntimeOutput {
    // context.req.body        — 原始请求体(字符串)
    // context.req.bodyJson    — 解析后的 JSON ([String: Any]?)
    // context.req.headers     — 请求头 ([String: String])
    // context.req.method      — HTTP 请求方法
    // context.req.path        — URL 路径
    // context.req.query       — 查询参数 ([String: String])

    context.log("Processing: \(context.req.method) \(context.req.path)")

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

    return context.res.json(["success": true])       // 返回 JSON
    // context.res.text("Hello")                     // 返回纯文本
    // context.res.empty()                           // 返回 204 空响应
    // context.res.redirect("https://...")            // 重定向
}

Server-Side Rendering (SSR) Authentication

服务端渲染(SSR)认证

SSR apps using server-side Swift (Vapor, Hummingbird, 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)
swift
import Appwrite

// Admin client (reusable)
let adminClient = Client()
    .setEndpoint("https://<REGION>.cloud.appwrite.io/v1")
    .setProject("[PROJECT_ID]")
    .setKey(Environment.get("APPWRITE_API_KEY")!)

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

if let session = req.cookies["a_session_[PROJECT_ID]"]?.string {
    sessionClient.setSession(session)
}
使用服务端 Swift(Vapor、Hummingbird 等)的 SSR 应用需使用服务端 SDK处理认证,需要两个客户端:
  • 管理员客户端:使用 API 密钥,可创建会话、绕过请求频率限制(可复用的单例)
  • 会话客户端:使用会话 Cookie,代表用户执行操作(每个请求创建一个,不可共享)
swift
import Appwrite

// 管理员客户端(可复用)
let adminClient = Client()
    .setEndpoint("https://<REGION>.cloud.appwrite.io/v1")
    .setProject("[PROJECT_ID]")
    .setKey(Environment.get("APPWRITE_API_KEY")!)

// 会话客户端(每个请求创建)
let sessionClient = Client()
    .setEndpoint("https://<REGION>.cloud.appwrite.io/v1")
    .setProject("[PROJECT_ID]")

if let session = req.cookies["a_session_[PROJECT_ID]"]?.string {
    sessionClient.setSession(session)
}

Email/Password Login (Vapor)

Vapor 中的邮箱/密码登录

swift
app.post("login") { req async throws -> Response in
    let body = try req.content.decode(LoginRequest.self)
    let account = Account(adminClient)
    let session = try await account.createEmailPasswordSession(
        email: body.email,
        password: body.password
    )

    // Cookie name must be a_session_<PROJECT_ID>
    let response = Response(status: .ok, body: .init(string: "{\"success\": true}"))
    response.cookies["a_session_[PROJECT_ID]"] = HTTPCookies.Value(
        string: session.secret,
        isHTTPOnly: true,
        isSecure: true,
        sameSite: .strict,
        path: "/"
    )
    return response
}
swift
app.post("login") { req async throws -> Response in
    let body = try req.content.decode(LoginRequest.self)
    let account = Account(adminClient)
    let session = try await account.createEmailPasswordSession(
        email: body.email,
        password: body.password
    )

    // Cookie 名称必须为 a_session_<PROJECT_ID>
    let response = Response(status: .ok, body: .init(string: "{\"success\": true}"))
    response.cookies["a_session_[PROJECT_ID]"] = HTTPCookies.Value(
        string: session.secret,
        isHTTPOnly: true,
        isSecure: true,
        sameSite: .strict,
        path: "/"
    )
    return response
}

Authenticated Requests

认证请求处理

swift
app.get("user") { req async throws -> Response in
    guard let session = req.cookies["a_session_[PROJECT_ID]"]?.string else {
        throw Abort(.unauthorized)
    }

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

    let account = Account(sessionClient)
    let user = try await account.get()
    // Return user as JSON
}
swift
app.get("user") { req async throws -> Response in
    guard let session = req.cookies["a_session_[PROJECT_ID]"]?.string else {
        throw Abort(.unauthorized)
    }

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

    let account = Account(sessionClient)
    let user = try await account.get()
    // 返回用户信息 JSON
}

OAuth2 SSR Flow

SSR 流程中的 OAuth2 登录

swift
// Step 1: Redirect to OAuth provider
app.get("oauth") { req async throws -> Response in
    let account = Account(adminClient)
    let redirectUrl = try await account.createOAuth2Token(
        provider: .github,
        success: "https://example.com/oauth/success",
        failure: "https://example.com/oauth/failure"
    )
    return req.redirect(to: redirectUrl)
}

// Step 2: Handle callback — exchange token for session
app.get("oauth", "success") { req async throws -> Response in
    let userId = try req.query.get(String.self, at: "userId")
    let secret = try req.query.get(String.self, at: "secret")

    let account = Account(adminClient)
    let session = try await account.createSession(userId: userId, secret: secret)

    let response = Response(status: .ok, body: .init(string: "{\"success\": true}"))
    response.cookies["a_session_[PROJECT_ID]"] = HTTPCookies.Value(
        string: session.secret,
        isHTTPOnly: true, isSecure: true, sameSite: .strict, path: "/"
    )
    return response
}
Cookie security: Always use
isHTTPOnly
,
isSecure
, and
sameSite: .strict
to prevent XSS. The cookie name must be
a_session_<PROJECT_ID>
.
Forwarding user agent: Call
sessionClient.setForwardedUserAgent(req.headers.first(name: .userAgent) ?? "")
to record the end-user's browser info for debugging and security.
swift
// 步骤1:重定向到 OAuth 提供商
app.get("oauth") { req async throws -> Response in
    let account = Account(adminClient)
    let redirectUrl = try await account.createOAuth2Token(
        provider: .github,
        success: "https://example.com/oauth/success",
        failure: "https://example.com/oauth/failure"
    )
    return req.redirect(to: redirectUrl)
}

// 步骤2:处理回调 — 交换令牌获取会话
app.get("oauth", "success") { req async throws -> Response in
    let userId = try req.query.get(String.self, at: "userId")
    let secret = try req.query.get(String.self, at: "secret")

    let account = Account(adminClient)
    let session = try await account.createSession(userId: userId, secret: secret)

    let response = Response(status: .ok, body: .init(string: "{\"success\": true}"))
    response.cookies["a_session_[PROJECT_ID]"] = HTTPCookies.Value(
        string: session.secret,
        isHTTPOnly: true, isSecure: true, sameSite: .strict, path: "/"
    )
    return response
}
Cookie 安全注意事项: 始终启用
isHTTPOnly
isSecure
sameSite: .strict
以防止 XSS 攻击。Cookie 名称必须为
a_session_<PROJECT_ID>
转发用户代理: 调用
sessionClient.setForwardedUserAgent(req.headers.first(name: .userAgent) ?? "")
可记录终端用户的浏览器信息,用于调试和安全审计。

Error Handling

错误处理

swift
import Appwrite
// AppwriteException is included in the main module

do {
    let row = try await tablesDB.getRow(databaseId: "[DATABASE_ID]", tableId: "[TABLE_ID]", rowId: "[ROW_ID]")
} catch let error as AppwriteException {
    print(error.message)     // human-readable message
    print(error.code)        // HTTP status code (Int)
    print(error.type)        // error type (e.g. "document_not_found")
    print(error.response)    // full response body
}
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
swift
import Appwrite
// AppwriteException 已包含在主模块导入中

do {
    let row = try await tablesDB.getRow(databaseId: "[DATABASE_ID]", tableId: "[TABLE_ID]", rowId: "[ROW_ID]")
} catch let error as AppwriteException {
    print(error.message)     // 易读错误信息
    print(error.code)        // HTTP 状态码(整数)
    print(error.type)        // 错误类型(例如 "document_not_found")
    print(error.response)    // 完整响应体
}
常见错误码:
状态码含义
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.
swift
import Appwrite
// Permission and Role are included in the main module import
Appwrite 使用权限字符串控制资源访问权限。每个权限由操作(
read
update
delete
create
,或包含创建、更新、删除的
write
)和角色目标组成。默认情况下,所有用户均无访问权限,除非在文档/文件级别明确设置权限,或从集合/存储桶设置中继承权限。权限是由
Permission
Role
工具类构建的字符串数组。
swift
import Appwrite
// Permission 和 Role 已包含在主模块导入中

Database Row with Permissions

带权限的数据库行

swift
let doc = try await tablesDB.createRow(
    databaseId: "[DATABASE_ID]",
    tableId: "[TABLE_ID]",
    rowId: ID.unique(),
    data: ["title": "Hello World"],
    permissions: [
        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
    ]
)
swift
let doc = try await tablesDB.createRow(
    databaseId: "[DATABASE_ID]",
    tableId: "[TABLE_ID]",
    rowId: ID.unique(),
    data: ["title": "Hello World"],
    permissions: [
        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

带权限的文件上传

swift
let file = try await storage.createFile(
    bucketId: "[BUCKET_ID]",
    fileId: ID.unique(),
    file: InputFile.fromPath("/path/to/file.png"),
    permissions: [
        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
swift
let file = try await storage.createFile(
    bucketId: "[BUCKET_ID]",
    fileId: ID.unique(),
    file: InputFile.fromPath("/path/to/file.png"),
    permissions: [
        Permission.read(Role.any()),
        Permission.update(Role.user("[USER_ID]")),
        Permission.delete(Role.user("[USER_ID]")),
    ]
)
权限设置时机: 当需要针对单个资源进行访问控制时,在文档/文件级别设置权限。如果集合中的所有文档共享相同的权限规则,可在集合/存储桶级别配置权限,文档级别留空即可。
常见错误:
  • 忘记设置权限 — 资源对所有用户(包括创建者)不可访问
  • write
    /
    update
    /
    delete
    使用
    Role.any()
    — 允许任何用户(包括未认证访客)修改或删除资源
  • 对敏感数据使用
    Permission.read(Role.any())
    — 导致资源可公开读取