appwrite-go — скилл для Claude Code: что делает и как установить
Скилл учит агента работать с официальным Appwrite Go SDK. Он помогает настраивать клиент, управлять пользователями, строить запросы и манипулировать таблицами в TablesDB.
npx skills add appwrite/skills --skill appwrite-go -a claude-codeЧто делает appwrite-go
Скилл добавляет в контекст правила для Go SDK (github.com/appwrite/sdk-for-go). Ассистент генерирует код с разделением по сервисам: users, tablesdb и storage. Для инициализации клиента он считывает параметры эндпоинта и секретный ключ из переменных окружения.
Вместо устаревшего класса Databases скилл предписывает использовать сервис TablesDB и функциональные опции вроде tablesdb.WithUpdateRowData. При создании таблиц задаются точные типы колонок. Тип varchar хранится прямо в строке и расходует общий лимит таблицы в 64 КБ, а типы text, mediumtext и longtext выносятся за её пределы.
Промпт также определяет синтаксис для построения фильтров, сортировок и загрузки файлов через фабрики file.NewInputFile. Без рабочего сервера Appwrite и API-ключа сгенерированные вызовы не выполнятся.
Какие задачи решает
- инициализировать клиент через
client.Newс ключами из окружения - создавать и обновлять записи через
TablesDBс функциональными опциями - выбирать типы текстовых колонок без превышения лимита строки в 64 КБ
- строить сложные выборки и объединения через
query.NewBuilder - загружать файлы в бакеты через
file.NewInputFile
Примеры запросов агенту
- Напиши Go-код для создания пользователя в Appwrite через пакет
users - Сделай таблицу articles в
TablesDBс колонкамиvarcharиmediumtext - Построй выборку строк с объединением через
query.LeftJoinи пагинацией
Как установить appwrite-go
1Через Skills CLI (Vercel)
npx skills add appwrite/skills --skill appwrite-go -a claude-code2Вручную: положить файлы
~/.claude/skills/appwrite-go/SKILL.mdПапка скилла целиком; для одного проекта — .claude/skills/appwrite-go/
1Через Skills CLI (Vercel)
npx skills add appwrite/skills --skill appwrite-go -a codex2Вручную: положить файлы
.agents/skills/appwrite-go/SKILL.mdДля всех проектов — ~/.agents/skills/
1Через Skills CLI (Vercel)
npx skills add appwrite/skills --skill appwrite-go -a cursor2Вручную: положить файлы
.cursor/skills/appwrite-go/SKILL.mdCursor читает и .agents/skills/, и .claude/skills/
1GitHub CLI
gh skill install appwrite/skills appwrite-go2Вручную: положить файлы
.github/skills/appwrite-go/SKILL.md1Через Skills CLI (Vercel)
npx skills add appwrite/skills --skill appwrite-go -a gemini-cli2Командой платформы
gemini skills install https://github.com/appwrite/skills --path skills/appwrite-goВручную: положить файлы
.devin/skills/appwrite-go/SKILL.mdСтарый путь .windsurf/skills/ тоже читается
Через Skills CLI (Vercel)
npx skills add appwrite/skills --skill appwrite-go -a opencodeClaude.ai
Упакуйте папку в zip и загрузите в Settings → Capabilities → Skills
По шагам, с командами для каждой платформы: Как установить скилл в Claude Code: пять способов
Вопросы о appwrite-go
Как установить appwrite-go в Claude Code?
Выполните команду npx skills add appwrite/skills --skill appwrite-go -a claude-code. Файл навыка сохранится по пути ~/.claude/skills/appwrite-go/SKILL.md.
Что делает скилл appwrite-go?
Он передаёт агенту правила работы с Go SDK от Appwrite, исключает устаревший класс Databases в пользу TablesDB и настраивает генерацию запросов через функциональные опции.
Почему скилл запрещает старый тип string в таблицах?
Тип string устарел. Вместо него скилл требует указывать varchar для коротких индексируемых полей внутри лимита 64 КБ или типы text, mediumtext и longtext, которые сохраняются вне страницы.
Исходный текст
SKILL.md · BSD-3-Clause
---
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.
---
# Appwrite Go SDK
## Installation
```bash
go get github.com/appwrite/sdk-for-go
```
## Setting Up the Client
```go
import (
"os"
"github.com/appwrite/sdk-for-go/client"
"github.com/appwrite/sdk-for-go/id"
"github.com/appwrite/sdk-for-go/users"
"github.com/appwrite/sdk-for-go/tablesdb"
"github.com/appwrite/sdk-for-go/storage"
)
clt := client.New(
client.WithEndpoint("https://<REGION>.cloud.appwrite.io/v1"),
client.WithProject(os.Getenv("APPWRITE_PROJECT_ID")),
client.WithKey(os.Getenv("APPWRITE_API_KEY")),
)
```
## Code Examples
### User Management
```go
service := users.New(clt)
// Create user
user, err := service.Create(
id.Unique(),
"[email protected]",
"password123",
users.WithCreateName("User Name"),
)
// List users
list, err := service.List()
// Get user
fetched, err := service.Get("[USER_ID]")
// Delete user
_, err = service.Delete("[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 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.
```go
service := tablesdb.New(clt)
// Create database
db, err := service.Create(id.Unique(), "My Database")
// Create row
doc, err := service.CreateRow(
"[DATABASE_ID]",
"[TABLE_ID]",
id.Unique(),
map[string]interface{}{"title": "Hello World"},
)
// List rows
results, err := service.ListRows("[DATABASE_ID]", "[TABLE_ID]")
// Get row
row, err := service.GetRow("[DATABASE_ID]", "[TABLE_ID]", "[ROW_ID]")
// Update row
_, err = service.UpdateRow(
"[DATABASE_ID]",
"[TABLE_ID]",
"[ROW_ID]",
tablesdb.WithUpdateRowData(map[string]interface{}{"title": "Updated"}),
)
// Delete row
_, err = service.DeleteRow("[DATABASE_ID]", "[TABLE_ID]", "[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.
```go
// Create table with explicit string column types
_, err = service.CreateTable(
"[DATABASE_ID]",
id.Unique(),
"articles",
tablesdb.WithCreateTableColumns([]map[string]interface{}{
{"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
```go
import "github.com/appwrite/sdk-for-go/query"
// Filtering
query.Equal("field", "value") // == (or pass slice 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([]string{"field1", "field2"})
query.Or([]string{query.Equal("a", 1), query.Equal("b", 2)}) // OR
query.And([]string{query.GreaterThan("age", 18), query.LessThan("age", 65)}) // AND (default)
query.Relationship("comments", []string{query.Equal("approved", true), query.Limit(25)}) // related documents
// Aggregations (optional alias)
query.Count("*", "total")
query.Sum("amount", "revenue")
query.Avg("score", "avgScore")
query.Min("price", "minPrice")
query.Max("price", "maxPrice")
// Grouping
query.GroupBy([]interface{}{"status", "year"})
query.Having([]string{query.GreaterThan("total", 1)})
query.Distinct()
// Joins (table id, left key, right key, operator, alias)
query.Join("orders", "$id", "customerId", "=", "")
query.LeftJoin("orders", "$id", "customerId", "=", "ord")
query.LeftJoinOn("orders", "ord", []string{
query.On("$id", "customerId", ""),
query.Equal("ord.status", "paid"),
})
// Fluent builder. Pass Build() into list methods.
queries := query.NewBuilder().
Equal("status", "published").
LeftJoin("orders", "$id", "customerId", "=", "ord").
Count("*", "total").
GroupBy([]interface{}{"status"}).
Limit(25).
Build()
_, _ = service.ListRows("[DATABASE_ID]", "[TABLE_ID]", service.WithListRowsQueries(queries))
```
### File Storage
```go
import "github.com/appwrite/sdk-for-go/file"
service := storage.New(clt)
// Upload file
f, err := service.CreateFile(
"[BUCKET_ID]",
"[FILE_ID]",
file.NewInputFile("/path/to/file.png", "file.png"),
)
// List files
files, err := service.ListFiles("[BUCKET_ID]")
// Delete file
_, err = service.DeleteFile("[BUCKET_ID]", "[FILE_ID]")
```
#### InputFile Factory Methods
```go
import "github.com/appwrite/sdk-for-go/file"
file.NewInputFile("/path/to/file.png", "file.png") // from filesystem path
file.NewInputFileFromReader(reader, "file.png", size) // from io.Reader (size required)
file.NewInputFileFromBytes(data, "file.png") // from []byte
```
### Teams
```go
import "github.com/appwrite/sdk-for-go/teams"
svc := teams.New(clt)
// Create team
team, err := svc.Create(id.Unique(), "Engineering")
// List teams
list, err := svc.List()
// Create membership (invite user by email)
membership, err := svc.CreateMembership(
"[TEAM_ID]",
[]string{"editor"},
teams.WithCreateMembershipEmail("[email protected]"),
)
// List memberships
members, err := svc.ListMemberships("[TEAM_ID]")
// Update membership roles
_, err = svc.UpdateMembership("[TEAM_ID]", "[MEMBERSHIP_ID]", []string{"admin"})
// Delete team
_, err = svc.Delete("[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.
### Serverless Functions
```go
import "github.com/appwrite/sdk-for-go/functions"
svc := functions.New(clt)
// Execute function
execution, err := svc.CreateExecution(
"[FUNCTION_ID]",
functions.WithCreateExecutionBody(`{"key": "value"}`),
)
// List executions
executions, err := svc.ListExecutions("[FUNCTION_ID]")
```
#### Writing a Function Handler (Go runtime)
```go
// src/main.go — Appwrite Function entry point
package handler
import (
"github.com/open-runtimes/types-for-go/v4/openruntimes"
)
func Main(context openruntimes.Context) openruntimes.Response {
// context.Req.Body — raw body (string)
// context.Req.BodyJson — parsed JSON (map[string]interface{})
// context.Req.Headers — headers (map[string]string)
// context.Req.Method — HTTP method
// context.Req.Path — URL path
// context.Req.Query — query params (map[string]string)
context.Log("Processing: " + context.Req.Method + " " + context.Req.Path)
if context.Req.Method == "GET" {
return context.Res.Json(map[string]interface{}{"message": "Hello!"})
}
return context.Res.Json(map[string]interface{}{"success": true})
// context.Res.Text("Hello") // plain text
// context.Res.Empty() // 204
// context.Res.Redirect("https://...") // 302
}
```
### Server-Side Rendering (SSR) Authentication
SSR apps using Go frameworks (net/http, Gin, Echo, Chi, 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)
```go
import (
"github.com/appwrite/sdk-for-go/client"
"github.com/appwrite/sdk-for-go/account"
)
// Admin client (reusable)
adminClient := client.New(
client.WithEndpoint("https://<REGION>.cloud.appwrite.io/v1"),
client.WithProject(os.Getenv("APPWRITE_PROJECT_ID")),
client.WithKey(os.Getenv("APPWRITE_API_KEY")),
)
// Session client (create per-request)
sessionClient := client.New(
client.WithEndpoint("https://<REGION>.cloud.appwrite.io/v1"),
client.WithProject(os.Getenv("APPWRITE_PROJECT_ID")),
)
cookie, err := r.Cookie("a_session_[PROJECT_ID]")
if err == nil {
sessionClient.SetSession(cookie.Value)
}
```
#### Email/Password Login
```go
http.HandleFunc("/login", func(w http.ResponseWriter, r *http.Request) {
svc := account.New(adminClient)
session, err := svc.CreateEmailPasswordSession(r.FormValue("email"), r.FormValue("password"))
if err != nil {
http.Error(w, err.Error(), http.StatusBadRequest)
return
}
// Cookie name must be a_session_<PROJECT_ID>
http.SetCookie(w, &http.Cookie{
Name: "a_session_[PROJECT_ID]",
Value: session.Secret,
HttpOnly: true,
Secure: true,
SameSite: http.SameSiteStrictMode,
Path: "/",
})
w.Header().Set("Content-Type", "application/json")
w.Write([]byte(`{"success": true}`))
})
```
#### Authenticated Requests
```go
http.HandleFunc("/user", func(w http.ResponseWriter, r *http.Request) {
cookie, err := r.Cookie("a_session_[PROJECT_ID]")
if err != nil {
http.Error(w, "Unauthorized", http.StatusUnauthorized)
return
}
sessionClient := client.New(
client.WithEndpoint("https://<REGION>.cloud.appwrite.io/v1"),
client.WithProject(os.Getenv("APPWRITE_PROJECT_ID")),
client.WithSession(cookie.Value),
)
svc := account.New(sessionClient)
user, err := svc.Get()
// Marshal user to JSON and write response
})
```
#### OAuth2 SSR Flow
```go
// Step 1: Redirect to OAuth provider
http.HandleFunc("/oauth", func(w http.ResponseWriter, r *http.Request) {
svc := account.New(adminClient)
redirectURL, err := svc.CreateOAuth2Token(
"github",
account.WithCreateOAuth2TokenSuccess("https://example.com/oauth/success"),
account.WithCreateOAuth2TokenFailure("https://example.com/oauth/failure"),
)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
http.Redirect(w, r, redirectURL, http.StatusFound)
})
// Step 2: Handle callback — exchange token for session
http.HandleFunc("/oauth/success", func(w http.ResponseWriter, r *http.Request) {
svc := account.New(adminClient)
session, err := svc.CreateSession(r.URL.Query().Get("userId"), r.URL.Query().Get("secret"))
if err != nil {
http.Error(w, err.Error(), http.StatusBadRequest)
return
}
http.SetCookie(w, &http.Cookie{
Name: "a_session_[PROJECT_ID]", Value: session.Secret,
HttpOnly: true, Secure: true, SameSite: http.SameSiteStrictMode, Path: "/",
})
w.Write([]byte(`{"success": true}`))
})
```
> **Cookie security:** Always use `HttpOnly`, `Secure`, and `SameSiteStrictMode` to prevent XSS. The cookie name must be `a_session_<PROJECT_ID>`.
> **Forwarding user agent:** Call `sessionClient.SetForwardedUserAgent(r.Header.Get("User-Agent"))` to record the end-user's browser info for debugging and security.
## Error Handling
```go
import "github.com/appwrite/sdk-for-go/apperr"
doc, err := service.GetRow("[DATABASE_ID]", "[TABLE_ID]", "[ROW_ID]")
if err != nil {
var appErr *apperr.AppwriteException
if errors.As(err, &appErr) {
fmt.Println(appErr.Message) // human-readable message
fmt.Println(appErr.Code) // HTTP status code (int)
fmt.Println(appErr.Type) // error type (e.g. "document_not_found")
}
}
```
**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.
```go
import (
"github.com/appwrite/sdk-for-go/permission"
"github.com/appwrite/sdk-for-go/role"
)
```
### Database Row with Permissions
```go
doc, err := service.CreateRow(
"[DATABASE_ID]",
"[TABLE_ID]",
"[ROW_ID]",
map[string]interface{}{"title": "Hello World"},
tablesdb.WithCreateRowPermissions([]string{
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
```go
f, err := service.CreateFile(
"[BUCKET_ID]",
"[FILE_ID]",
file.NewInputFile("/path/to/file.png", "file.png"),
storage.WithCreateFilePermissions([]string{
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
© appwrite · BSD-3-Clause · appwrite/skills