← AppwriteCONTENT HISTORYWHAT CHANGED · RULE-BASED ANALYSIS
Update to Appwrite
Snapshot Sep 30, 2026 · 23:07 UTC · version 1.0.0
Collection source: not recorded for this historical snapshot.
First saved snapshot
No earlier snapshot is available to establish a change.
Compare saved observations
Download comparison JSONFull technical diff · 0 changed fields
Full snapshot data
{
"name": "appwrite-go",
"description": "Appwrite Go SDK skill. Use when building server-side Go applications with Appwrite. Covers user management, database/table CRUD, file storage, and functions via API keys. Uses per-service packages and functional options pattern.",
"included_files": [],
"skill_md_contents": "---\r\nname: appwrite-go\r\ndescription: Appwrite Go SDK skill. Use when building server-side Go applications with Appwrite. Covers user management, database/table CRUD, file storage, and functions via API keys. Uses per-service packages and functional options pattern.\r\n---\r\n\r\n\r\n# Appwrite Go SDK\r\n\r\n## Installation\r\n\r\n```bash\r\ngo get github.com/appwrite/sdk-for-go\r\n```\r\n\r\n## Setting Up the Client\r\n\r\n```go\r\nimport (\r\n \"os\"\r\n\r\n \"github.com/appwrite/sdk-for-go/client\"\r\n \"github.com/appwrite/sdk-for-go/id\"\r\n \"github.com/appwrite/sdk-for-go/users\"\r\n \"github.com/appwrite/sdk-for-go/tablesdb\"\r\n \"github.com/appwrite/sdk-for-go/storage\"\r\n)\r\n\r\nclt := client.New(\r\n client.WithEndpoint(\"https://<REGION>.cloud.appwrite.io/v1\"),\r\n client.WithProject(os.Getenv(\"APPWRITE_PROJECT_ID\")),\r\n client.WithKey(os.Getenv(\"APPWRITE_API_KEY\")),\r\n)\r\n```\r\n\r\n## Code Examples\r\n\r\n### User Management\r\n\r\n```go\r\nservice := users.New(clt)\r\n\r\n// Create user\r\nuser, err := service.Create(\r\n id.Unique(),\r\n \"user@example.com\",\r\n \"password123\",\r\n users.WithCreateName(\"User Name\"),\r\n)\r\n\r\n// List users\r\nlist, err := service.List()\r\n\r\n// Get user\r\nfetched, err := service.Get(\"[USER_ID]\")\r\n\r\n// Delete user\r\n_, err = service.Delete(\"[USER_ID]\")\r\n```\r\n\r\n### Database Operations\r\n\r\n> **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.\r\n>\r\n> **Tip:** Prefer explicit functional option parameters (e.g., `tablesdb.WithUpdateRowData(...)`) over bare positional arguments where available. Only use positional-only style if the existing codebase already uses it or the user explicitly requests it.\r\n\r\n```go\r\nservice := tablesdb.New(clt)\r\n\r\n// Create database\r\ndb, err := service.Create(id.Unique(), \"My Database\")\r\n\r\n// Create row\r\ndoc, err := service.CreateRow(\r\n \"[DATABASE_ID]\",\r\n \"[TABLE_ID]\",\r\n id.Unique(),\r\n map[string]interface{}{\"title\": \"Hello World\"},\r\n)\r\n\r\n// List rows\r\nresults, err := service.ListRows(\"[DATABASE_ID]\", \"[TABLE_ID]\")\r\n\r\n// Get row\r\nrow, err := service.GetRow(\"[DATABASE_ID]\", \"[TABLE_ID]\", \"[ROW_ID]\")\r\n\r\n// Update row\r\n_, err = service.UpdateRow(\r\n \"[DATABASE_ID]\",\r\n \"[TABLE_ID]\",\r\n \"[ROW_ID]\",\r\n tablesdb.WithUpdateRowData(map[string]interface{}{\"title\": \"Updated\"}),\r\n)\r\n\r\n// Delete row\r\n_, err = service.DeleteRow(\"[DATABASE_ID]\", \"[TABLE_ID]\", \"[ROW_ID]\")\r\n```\r\n\r\n#### String Column Types\r\n\r\n> **Note:** The legacy `string` type is deprecated. Use explicit column types for all new columns.\r\n\r\n| Type | Max characters | Indexing | Storage |\r\n|------|---------------|----------|---------|\r\n| `varchar` | 16,383 | Full index (if size ≤ 768) | Inline in row |\r\n| `text` | 16,383 | Prefix only | Off-page |\r\n| `mediumtext` | 4,194,303 | Prefix only | Off-page |\r\n| `longtext` | 1,073,741,823 | Prefix only | Off-page |\r\n\r\n- `varchar` is stored inline and counts towards the 64 KB row size limit. Prefer for short, indexed fields like names, slugs, or identifiers.\r\n- `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.\r\n\r\n```go\r\n// Create table with explicit string column types\r\n_, err = service.CreateTable(\r\n \"[DATABASE_ID]\",\r\n id.Unique(),\r\n \"articles\",\r\n tablesdb.WithCreateTableColumns([]map[string]interface{}{\r\n {\"key\": \"title\", \"type\": \"varchar\", \"size\": 255, \"required\": true},\r\n {\"key\": \"summary\", \"type\": \"text\", \"required\": false},\r\n {\"key\": \"body\", \"type\": \"mediumtext\", \"required\": false},\r\n {\"key\": \"raw_data\", \"type\": \"longtext\", \"required\": false},\r\n }),\r\n)\r\n```\r\n\r\n### Query Methods\r\n\r\n```go\r\nimport \"github.com/appwrite/sdk-for-go/query\"\r\n\r\n// Filtering\r\nquery.Equal(\"field\", \"value\") // == (or pass slice for IN)\r\nquery.NotEqual(\"field\", \"value\") // !=\r\nquery.LessThan(\"field\", 100) // <\r\nquery.LessThanEqual(\"field\", 100) // <=\r\nquery.GreaterThan(\"field\", 100) // >\r\nquery.GreaterThanEqual(\"field\", 100) // >=\r\nquery.Between(\"field\", 1, 100) // 1 <= field <= 100\r\nquery.IsNull(\"field\") // is null\r\nquery.IsNotNull(\"field\") // is not null\r\nquery.StartsWith(\"field\", \"prefix\") // starts with\r\nquery.EndsWith(\"field\", \"suffix\") // ends with\r\nquery.Contains(\"field\", \"sub\") // contains\r\nquery.Search(\"field\", \"keywords\") // full-text search (requires index)\r\n\r\n// Sorting\r\nquery.OrderAsc(\"field\")\r\nquery.OrderDesc(\"field\")\r\n\r\n// Pagination\r\nquery.Limit(25) // max rows (default 25, max 100)\r\nquery.Offset(0) // skip N rows\r\nquery.CursorAfter(\"[ROW_ID]\") // cursor pagination (preferred)\r\nquery.CursorBefore(\"[ROW_ID]\")\r\n\r\n// Selection & Logic\r\nquery.Select([]string{\"field1\", \"field2\"})\r\nquery.Or([]string{query.Equal(\"a\", 1), query.Equal(\"b\", 2)}) // OR\r\nquery.And([]string{query.GreaterThan(\"age\", 18), query.LessThan(\"age\", 65)}) // AND (default)\r\n```\r\n\r\n### File Storage\r\n\r\n```go\r\nimport \"github.com/appwrite/sdk-for-go/file\"\r\n\r\nservice := storage.New(clt)\r\n\r\n// Upload file\r\nf, err := service.CreateFile(\r\n \"[BUCKET_ID]\",\r\n \"[FILE_ID]\",\r\n file.NewInputFile(\"/path/to/file.png\", \"file.png\"),\r\n)\r\n\r\n// List files\r\nfiles, err := service.ListFiles(\"[BUCKET_ID]\")\r\n\r\n// Delete file\r\n_, err = service.DeleteFile(\"[BUCKET_ID]\", \"[FILE_ID]\")\r\n```\r\n\r\n#### InputFile Factory Methods\r\n\r\n```go\r\nimport \"github.com/appwrite/sdk-for-go/file\"\r\n\r\nfile.NewInputFile(\"/path/to/file.png\", \"file.png\") // from filesystem path\r\nfile.NewInputFileFromReader(reader, \"file.png\", size) // from io.Reader (size required)\r\nfile.NewInputFileFromBytes(data, \"file.png\") // from []byte\r\n```\r\n\r\n### Teams\r\n\r\n```go\r\nimport \"github.com/appwrite/sdk-for-go/teams\"\r\n\r\nsvc := teams.New(clt)\r\n\r\n// Create team\r\nteam, err := svc.Create(id.Unique(), \"Engineering\")\r\n\r\n// List teams\r\nlist, err := svc.List()\r\n\r\n// Create membership (invite user by email)\r\nmembership, err := svc.CreateMembership(\r\n \"[TEAM_ID]\",\r\n []string{\"editor\"},\r\n teams.WithCreateMembershipEmail(\"user@example.com\"),\r\n)\r\n\r\n// List memberships\r\nmembers, err := svc.ListMemberships(\"[TEAM_ID]\")\r\n\r\n// Update membership roles\r\n_, err = svc.UpdateMembership(\"[TEAM_ID]\", \"[MEMBERSHIP_ID]\", []string{\"admin\"})\r\n\r\n// Delete team\r\n_, err = svc.Delete(\"[TEAM_ID]\")\r\n```\r\n\r\n> **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.\r\n\r\n### Serverless Functions\r\n\r\n```go\r\nimport \"github.com/appwrite/sdk-for-go/functions\"\r\n\r\nsvc := functions.New(clt)\r\n\r\n// Execute function\r\nexecution, err := svc.CreateExecution(\r\n \"[FUNCTION_ID]\",\r\n functions.WithCreateExecutionBody(`{\"key\": \"value\"}`),\r\n)\r\n\r\n// List executions\r\nexecutions, err := svc.ListExecutions(\"[FUNCTION_ID]\")\r\n```\r\n\r\n#### Writing a Function Handler (Go runtime)\r\n\r\n```go\r\n// src/main.go — Appwrite Function entry point\r\npackage handler\r\n\r\nimport (\r\n \"github.com/open-runtimes/types-for-go/v4/openruntimes\"\r\n)\r\n\r\nfunc Main(context openruntimes.Context) openruntimes.Response {\r\n // context.Req.Body — raw body (string)\r\n // context.Req.BodyJson — parsed JSON (map[string]interface{})\r\n // context.Req.Headers — headers (map[string]string)\r\n // context.Req.Method — HTTP method\r\n // context.Req.Path — URL path\r\n // context.Req.Query — query params (map[string]string)\r\n\r\n context.Log(\"Processing: \" + context.Req.Method + \" \" + context.Req.Path)\r\n\r\n if context.Req.Method == \"GET\" {\r\n return context.Res.Json(map[string]interface{}{\"message\": \"Hello!\"})\r\n }\r\n\r\n return context.Res.Json(map[string]interface{}{\"success\": true})\r\n // context.Res.Text(\"Hello\") // plain text\r\n // context.Res.Empty() // 204\r\n // context.Res.Redirect(\"https://...\") // 302\r\n}\r\n```\r\n\r\n### Server-Side Rendering (SSR) Authentication\r\n\r\nSSR apps using Go frameworks (net/http, Gin, Echo, Chi, etc.) use the **server SDK** to handle auth. You need two clients:\r\n\r\n- **Admin client** — uses an API key, creates sessions, bypasses rate limits (reusable singleton)\r\n- **Session client** — uses a session cookie, acts on behalf of a user (create per-request, never share)\r\n\r\n```go\r\nimport (\r\n \"github.com/appwrite/sdk-for-go/client\"\r\n \"github.com/appwrite/sdk-for-go/account\"\r\n)\r\n\r\n// Admin client (reusable)\r\nadminClient := client.New(\r\n client.WithEndpoint(\"https://<REGION>.cloud.appwrite.io/v1\"),\r\n client.WithProject(os.Getenv(\"APPWRITE_PROJECT_ID\")),\r\n client.WithKey(os.Getenv(\"APPWRITE_API_KEY\")),\r\n)\r\n\r\n// Session client (create per-request)\r\nsessionClient := client.New(\r\n client.WithEndpoint(\"https://<REGION>.cloud.appwrite.io/v1\"),\r\n client.WithProject(os.Getenv(\"APPWRITE_PROJECT_ID\")),\r\n)\r\n\r\ncookie, err := r.Cookie(\"a_session_[PROJECT_ID]\")\r\nif err == nil {\r\n sessionClient.SetSession(cookie.Value)\r\n}\r\n```\r\n\r\n#### Email/Password Login\r\n\r\n```go\r\nhttp.HandleFunc(\"/login\", func(w http.ResponseWriter, r *http.Request) {\r\n svc := account.New(adminClient)\r\n session, err := svc.CreateEmailPasswordSession(r.FormValue(\"email\"), r.FormValue(\"password\"))\r\n if err != nil {\r\n http.Error(w, err.Error(), http.StatusBadRequest)\r\n return\r\n }\r\n\r\n // Cookie name must be a_session_<PROJECT_ID>\r\n http.SetCookie(w, &http.Cookie{\r\n Name: \"a_session_[PROJECT_ID]\",\r\n Value: session.Secret,\r\n HttpOnly: true,\r\n Secure: true,\r\n SameSite: http.SameSiteStrictMode,\r\n Path: \"/\",\r\n })\r\n\r\n w.Header().Set(\"Content-Type\", \"application/json\")\r\n w.Write([]byte(`{\"success\": true}`))\r\n})\r\n```\r\n\r\n#### Authenticated Requests\r\n\r\n```go\r\nhttp.HandleFunc(\"/user\", func(w http.ResponseWriter, r *http.Request) {\r\n cookie, err := r.Cookie(\"a_session_[PROJECT_ID]\")\r\n if err != nil {\r\n http.Error(w, \"Unauthorized\", http.StatusUnauthorized)\r\n return\r\n }\r\n\r\n sessionClient := client.New(\r\n client.WithEndpoint(\"https://<REGION>.cloud.appwrite.io/v1\"),\r\n client.WithProject(os.Getenv(\"APPWRITE_PROJECT_ID\")),\r\n client.WithSession(cookie.Value),\r\n )\r\n\r\n svc := account.New(sessionClient)\r\n user, err := svc.Get()\r\n // Marshal user to JSON and write response\r\n})\r\n```\r\n\r\n#### OAuth2 SSR Flow\r\n\r\n```go\r\n// Step 1: Redirect to OAuth provider\r\nhttp.HandleFunc(\"/oauth\", func(w http.ResponseWriter, r *http.Request) {\r\n svc := account.New(adminClient)\r\n redirectURL, err := svc.CreateOAuth2Token(\r\n \"github\",\r\n account.WithCreateOAuth2TokenSuccess(\"https://example.com/oauth/success\"),\r\n account.WithCreateOAuth2TokenFailure(\"https://example.com/oauth/failure\"),\r\n )\r\n if err != nil {\r\n http.Error(w, err.Error(), http.StatusInternalServerError)\r\n return\r\n }\r\n http.Redirect(w, r, redirectURL, http.StatusFound)\r\n})\r\n\r\n// Step 2: Handle callback — exchange token for session\r\nhttp.HandleFunc(\"/oauth/success\", func(w http.ResponseWriter, r *http.Request) {\r\n svc := account.New(adminClient)\r\n session, err := svc.CreateSession(r.URL.Query().Get(\"userId\"), r.URL.Query().Get(\"secret\"))\r\n if err != nil {\r\n http.Error(w, err.Error(), http.StatusBadRequest)\r\n return\r\n }\r\n\r\n http.SetCookie(w, &http.Cookie{\r\n Name: \"a_session_[PROJECT_ID]\", Value: session.Secret,\r\n HttpOnly: true, Secure: true, SameSite: http.SameSiteStrictMode, Path: \"/\",\r\n })\r\n w.Write([]byte(`{\"success\": true}`))\r\n})\r\n```\r\n\r\n> **Cookie security:** Always use `HttpOnly`, `Secure`, and `SameSiteStrictMode` to prevent XSS. The cookie name must be `a_session_<PROJECT_ID>`.\r\n\r\n> **Forwarding user agent:** Call `sessionClient.SetForwardedUserAgent(r.Header.Get(\"User-Agent\"))` to record the end-user's browser info for debugging and security.\r\n\r\n## Error Handling\r\n\r\n```go\r\nimport \"github.com/appwrite/sdk-for-go/apperr\"\r\n\r\ndoc, err := service.GetRow(\"[DATABASE_ID]\", \"[TABLE_ID]\", \"[ROW_ID]\")\r\nif err != nil {\r\n var appErr *apperr.AppwriteException\r\n if errors.As(err, &appErr) {\r\n fmt.Println(appErr.Message) // human-readable message\r\n fmt.Println(appErr.Code) // HTTP status code (int)\r\n fmt.Println(appErr.Type) // error type (e.g. \"document_not_found\")\r\n }\r\n}\r\n```\r\n\r\n**Common error codes:**\r\n\r\n| Code | Meaning |\r\n|------|---------|\r\n| `401` | Unauthorized — missing or invalid session/API key |\r\n| `403` | Forbidden — insufficient permissions |\r\n| `404` | Not found — resource does not exist |\r\n| `409` | Conflict — duplicate ID or unique constraint |\r\n| `429` | Rate limited — too many requests |\r\n\r\n## Permissions & Roles (Critical)\r\n\r\nAppwrite 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.\r\n\r\n```go\r\nimport (\r\n \"github.com/appwrite/sdk-for-go/permission\"\r\n \"github.com/appwrite/sdk-for-go/role\"\r\n)\r\n```\r\n\r\n### Database Row with Permissions\r\n\r\n```go\r\ndoc, err := service.CreateRow(\r\n \"[DATABASE_ID]\",\r\n \"[TABLE_ID]\",\r\n \"[ROW_ID]\",\r\n map[string]interface{}{\"title\": \"Hello World\"},\r\n tablesdb.WithCreateRowPermissions([]string{\r\n permission.Read(role.User(\"[USER_ID]\")), // specific user can read\r\n permission.Update(role.User(\"[USER_ID]\")), // specific user can update\r\n permission.Read(role.Team(\"[TEAM_ID]\")), // all team members can read\r\n permission.Read(role.Any()), // anyone (including guests) can read\r\n }),\r\n)\r\n```\r\n\r\n### File Upload with Permissions\r\n\r\n```go\r\nf, err := service.CreateFile(\r\n \"[BUCKET_ID]\",\r\n \"[FILE_ID]\",\r\n file.NewInputFile(\"/path/to/file.png\", \"file.png\"),\r\n storage.WithCreateFilePermissions([]string{\r\n permission.Read(role.Any()),\r\n permission.Update(role.User(\"[USER_ID]\")),\r\n permission.Delete(role.User(\"[USER_ID]\")),\r\n }),\r\n)\r\n```\r\n\r\n> **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.\r\n\r\n> **Common mistakes:**\r\n> - **Forgetting permissions** — the resource becomes inaccessible to all users (including the creator)\r\n> - **`role.Any()` with `write`/`update`/`delete`** — allows any user, including unauthenticated guests, to modify or remove the resource\r\n> - **`permission.Read(role.Any())` on sensitive data** — makes the resource publicly readable\r\n\r\n"
}SHA-256: bd39048bad968d48ec36c0cbf414dae65f1e47beeafe086cc22d983b6576f229