Swift

by appwriteab3c90b37c95No license10 starsListed Oct 8, 2026Updated Oct 8, 2026Repository updated 7 weeks ago

Appwrite Swift SDK skill. Use when building native iOS, macOS, watchOS, or tvOS apps, or server-side Swift applications with Appwrite. Covers client-side auth (email, OAuth), database queries, file uploads, real-time subscriptions with async/await, and server-side admin via API keys for user management, database administration, storage, and functions.

Instructions onlySoftware Development
AI-generated overview

Reference guide for using the Appwrite Swift SDK in Apple-platform and server-side Swift apps.

What it does
Provides Swift code examples and guidance for the Appwrite SDK, covering client setup, authentication, database and table operations, queries, file storage, teams, real-time subscriptions, serverless functions, SSR authentication, error handling, and permissions. It is an instruction-only reference with no scripts or assets.
When to use it
Use when building native iOS, macOS, watchOS, or tvOS apps, or server-side Swift applications, that integrate with Appwrite. Also useful when implementing Appwrite auth, database queries, storage, real-time updates, or permission rules in Swift.
Requirements
No scripts ship with the skill. Using the examples requires the Appwrite Swift SDK via Swift Package Manager, an Appwrite project endpoint and project ID, and for server-side admin operations an API key. Network access to an Appwrite instance is needed at runtime.

Appwrite Swift SDK

Installation

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)

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

Server-side (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"]!)

Code Examples

Authentication (client-side)

swift
let account = Account(client)
// Signuplet user = try await account.create(userId: ID.unique(), email: "[email protected]", password: "password123", name: "User Name")
// Loginlet session = try await account.createEmailPasswordSession(email: "[email protected]", password: "password123")
// OAuthtry await account.createOAuth2Session(provider: .google)
// Get current userlet me = try await account.get()
// Logouttry await account.deleteSession(sessionId: "current")

User Management (server-side)

swift
let users = Users(client)
// Create userlet user = try await users.create(userId: ID.unique(), email: "[email protected]", password: "password123", name: "User Name")
// List userslet list = try await users.list(queries: [Query.limit(25)])
// Get userlet fetched = try await users.get(userId: "[USER_ID]")
// Delete usertry 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 rowlet doc = try await tablesDB.createRow(databaseId: "[DATABASE_ID]", tableId: "[TABLE_ID]", rowId: ID.unique(), data: [    "title": "Hello",    "done": false])
// Query rowslet results = try await tablesDB.listRows(databaseId: "[DATABASE_ID]", tableId: "[TABLE_ID]", queries: [    Query.equal("done", value: false),    Query.limit(10)])
// Get rowlet row = try await tablesDB.getRow(databaseId: "[DATABASE_ID]", tableId: "[TABLE_ID]", rowId: "[ROW_ID]")
// Update rowtry await tablesDB.updateRow(databaseId: "[DATABASE_ID]", tableId: "[TABLE_ID]", rowId: "[ROW_ID]", data: ["done": true])
// Delete rowtry 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
varchar16,383Full index (if size ≤ 768)Inline in row
text16,383Prefix onlyOff-page
mediumtext4,194,303Prefix onlyOff-page
longtext1,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 typestry 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
// FilteringQuery.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 <= 100Query.isNull("field")                         // is nullQuery.isNotNull("field")                      // is not nullQuery.startsWith("field", value: "prefix")    // starts withQuery.endsWith("field", value: "suffix")      // ends withQuery.contains("field", value: "sub")         // containsQuery.search("field", value: "keywords")      // full-text search (requires index)
// SortingQuery.orderAsc("field")Query.orderDesc("field")
// PaginationQuery.limit(25)                               // max rows (default 25, max 100)Query.offset(0)                               // skip N rowsQuery.cursorAfter("[ROW_ID]")                 // cursor pagination (preferred)Query.cursorBefore("[ROW_ID]")
// Selection & LogicQuery.select(["field1", "field2"])Query.or([Query.equal("a", value: 1), Query.equal("b", value: 2)])   // ORQuery.and([Query.greaterThan("age", value: 18), Query.lessThan("age", value: 65)])  // AND (default)

File Storage

swift
let storage = Storage(client)
// Upload filelet file = try await storage.createFile(bucketId: "[BUCKET_ID]", fileId: ID.unique(), file: InputFile.fromPath("/path/to/file.png"))
// List fileslet files = try await storage.listFiles(bucketId: "[BUCKET_ID]")
// Delete filetry await storage.deleteFile(bucketId: "[BUCKET_ID]", fileId: "[FILE_ID]")
InputFile Factory Methods
swift
InputFile.fromPath("/path/to/file.png")                    // from filesystem pathInputFile.fromData(data, filename: "file.png", mimeType: "image/png")  // from Data

Teams

swift
let teams = Teams(client)
// Create teamlet team = try await teams.create(teamId: ID.unique(), name: "Engineering")
// List teamslet list = try await teams.list()
// Create membership (invite user by email)let membership = try await teams.createMembership(    teamId: "[TEAM_ID]",    roles: ["editor"],    email: "[email protected]")
// List membershipslet members = try await teams.listMemberships(teamId: "[TEAM_ID]")
// Update membership rolestry await teams.updateMembership(teamId: "[TEAM_ID]", membershipId: "[MEMBERSHIP_ID]", roles: ["admin"])
// Delete teamtry 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.

Real-time Subscriptions (client-side)

swift
let realtime = Realtime(client)
// Subscribe to row changeslet subscription = try await realtime.subscribe(channels: [    Channel.tablesdb("[DATABASE_ID]").table("[TABLE_ID]").row()]) { response in    print(response.events)   // e.g. ["tablesdb.*.tables.*.rows.*.create"]    print(response.payload)  // the affected resource}
// Subscribe to multiple channelslet multi = try await realtime.subscribe(channels: [    Channel.tablesdb("[DATABASE_ID]").table("[TABLE_ID]").row(),    Channel.files(),]) { response in /* ... */ }
// Cleanuptry await subscription.close()

Available channels:

ChannelDescription
accountChanges to the authenticated user's account
tablesdb.[DB_ID].tables.[TABLE_ID].rowsAll rows in a table
tablesdb.[DB_ID].tables.[TABLE_ID].rows.[ROW_ID]A specific row
buckets.[BUCKET_ID].filesAll files in a bucket
buckets.[BUCKET_ID].files.[FILE_ID]A specific file
teamsChanges to teams the user belongs to
teams.[TEAM_ID]A specific team
membershipsThe user's team memberships
functions.[FUNCTION_ID].executionsFunction execution updates

Response fields: events (array), payload (resource), channels (matched), timestamp (ISO 8601).

Serverless Functions (server-side)

swift
let functions = Functions(client)
// Execute functionlet execution = try await functions.createExecution(functionId: "[FUNCTION_ID]", body: "{\"key\": \"value\"}")
// List executionslet executions = try await functions.listExecutions(functionId: "[FUNCTION_ID]")
Writing a Function Handler (Swift runtime)
swift
// Sources/main.swift — Appwrite Function entry pointfunc 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}

Server-Side Rendering (SSR) Authentication

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)}
Email/Password Login (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}
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}
OAuth2 SSR Flow
swift
// Step 1: Redirect to OAuth providerapp.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 sessionapp.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.

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
401Unauthorized — missing or invalid session/API key
403Forbidden — insufficient permissions
404Not found — resource does not exist
409Conflict — duplicate ID or unique constraint
429Rate limited — too many requests

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 row/file level or inherited from the table/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

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    ])

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 row/file-level permissions when you need per-resource access control. If all rows in a table share the same rules, configure permissions at the table/bucket level and leave row 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

Source and attribution

Source:appwrite/claude-plugininskills/swiftat commitab3c90b

License: No license

Content belongs to its original authors. SourceWeft indexes it from a public repository.

Report or request removal