AgentStack
SKILL verified BSD-3-Clause Self-run

Appwrite Swift

skill-appwrite-agent-skills-appwrite-swift · by appwrite

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.

No reviews yet
0 installs
8 views
0.0% view→install

Install

$ agentstack add skill-appwrite-agent-skills-appwrite-swift

✓ scanned · ✓ verified — works with Claude Code, Cursor, and more.

Security review

✓ Passed

No issues found. Passed automated security review. · v0.1.0 How review works →

  • Prompt-injection patterns
  • Secret / credential exfiltration
  • Dangerous shell & filesystem operations
  • Untrusted network calls
  • Known-malicious package signatures

What it can access

  • Network access No
  • Filesystem access No
  • Shell / process execution No
  • Environment & secrets No
  • Dynamic code execution No

From automated source analysis of v0.1.0. “Used” means the capability is present in the source — more access means more to trust, not that it’s unsafe.

Are you the author of Appwrite Swift? Claim this listing to set pricing, connect Stripe payouts, and keep 70% of every sale.
Sign up to claim

About

Appwrite Swift SDK

Installation

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

Setting Up the Client

Client-side (Apple platforms)

import Appwrite

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

Server-side (Swift)

import Appwrite

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

Code Examples

Authentication (client-side)

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

User Management (server-side)

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

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.

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]")
String Column Types

> Note: The legacy string type is deprecated. Use explicit column types for all new columns.

| Type | Max characters | Indexing | Storage | |------|---------------|----------|---------| | varchar | 16,383 | Full index (if size ≤ 768) | Inline in row | | text | 16,383 | Prefix only | Off-page | | mediumtext | 4,194,303 | Prefix only | Off-page | | longtext | 1,073,741,823 | Prefix only | Off-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.
// 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],
    ]
)

Query Methods

// Filtering
Query.equal("field", value: "value")          // == (or pass array for IN)
Query.notEqual("field", value: "value")       // !=
Query.lessThan("field", value: 100)           // 
Query.greaterThanEqual("field", value: 100)   // >=
Query.between("field", start: 1, end: 100)    // 1  **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 changes
let 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 channels
let multi = try await realtime.subscribe(channels: [
    Channel.tablesdb("[DATABASE_ID]").table("[TABLE_ID]").row(),
    Channel.files(),
]) { response in /* ... */ }

// Cleanup
try await subscription.close()

Available channels:

| Channel | Description | |---------|-------------| | 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).

Serverless Functions (server-side)

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]")
Writing a Function Handler (Swift runtime)
// 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
}

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)
import Appwrite

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

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

if let session = req.cookies["a_session_[PROJECT_ID]"]?.string {
    sessionClient.setSession(session)
}
Email/Password Login (Vapor)
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_
    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
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://.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
// 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_.

> 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

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:

| Code | Meaning | |------|---------| | 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 |

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.

import Appwrite
// Permission and Role are included in the main module import

Database Row with Permissions

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

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 & license

This open-source skill is cataloged on AgentStack and links to its original source — we do not rehost the code.

Install and usage instructions live in the source repository linked above.

Reviews

No reviews yet — be the first.

Versions

  • v0.1.0 Imported from the upstream source.