← 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-dotnet",
"description": "Appwrite .NET SDK skill. Use when building server-side C# or .NET applications with Appwrite, including ASP.NET and Blazor integrations. Covers user management, database/table CRUD, file storage, and functions via API keys.",
"included_files": [],
"skill_md_contents": "---\r\nname: appwrite-dotnet\r\ndescription: Appwrite .NET SDK skill. Use when building server-side C# or .NET applications with Appwrite, including ASP.NET and Blazor integrations. Covers user management, database/table CRUD, file storage, and functions via API keys.\r\n---\r\n\r\n\r\n# Appwrite .NET SDK\r\n\r\n## Installation\r\n\r\n```bash\r\ndotnet add package Appwrite\r\n```\r\n\r\n## Setting Up the Client\r\n\r\n```csharp\r\nusing Appwrite;\r\nusing Appwrite.Services;\r\nusing Appwrite.Models;\r\n\r\nvar client = new Client()\r\n .SetEndpoint(\"https://<REGION>.cloud.appwrite.io/v1\")\r\n .SetProject(Environment.GetEnvironmentVariable(\"APPWRITE_PROJECT_ID\"))\r\n .SetKey(Environment.GetEnvironmentVariable(\"APPWRITE_API_KEY\"));\r\n```\r\n\r\n## Code Examples\r\n\r\n### User Management\r\n\r\n```csharp\r\nvar users = new Users(client);\r\n\r\n// Create user\r\nvar user = await users.Create(ID.Unique(), \"user@example.com\", null, \"password123\", \"User Name\");\r\n\r\n// List users\r\nvar list = await users.List(new List<string> { Query.Limit(25) });\r\n\r\n// Get user\r\nvar fetched = await users.Get(\"[USER_ID]\");\r\n\r\n// Delete user\r\nawait users.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 named arguments (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.\r\n\r\n```csharp\r\nvar tablesDB = new TablesDB(client);\r\n\r\n// Create database\r\nvar db = await tablesDB.Create(ID.Unique(), \"My Database\");\r\n\r\n// Create row\r\nvar doc = await tablesDB.CreateRow(\"[DATABASE_ID]\", \"[TABLE_ID]\", ID.Unique(),\r\n new Dictionary<string, object> { { \"title\", \"Hello World\" } });\r\n\r\n// Query rows\r\nvar results = await tablesDB.ListRows(\"[DATABASE_ID]\", \"[TABLE_ID]\",\r\n new List<string> { Query.Equal(\"title\", \"Hello World\"), Query.Limit(10) });\r\n\r\n// Get row\r\nvar row = await tablesDB.GetRow(\"[DATABASE_ID]\", \"[TABLE_ID]\", \"[ROW_ID]\");\r\n\r\n// Update row\r\nawait tablesDB.UpdateRow(\"[DATABASE_ID]\", \"[TABLE_ID]\", \"[ROW_ID]\",\r\n new Dictionary<string, object> { { \"title\", \"Updated\" } });\r\n\r\n// Delete row\r\nawait tablesDB.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```csharp\r\n// Create table with explicit string column types\r\nawait tablesDB.CreateTable(\"[DATABASE_ID]\", ID.Unique(), \"articles\",\r\n new List<object> {\r\n new { key = \"title\", type = \"varchar\", size = 255, required = true }, // inline, fully indexable\r\n new { key = \"summary\", type = \"text\", required = false }, // off-page, prefix index only\r\n new { key = \"body\", type = \"mediumtext\", required = false }, // up to ~4 M chars\r\n new { key = \"raw_data\", type = \"longtext\", required = false }, // up to ~1 B chars\r\n });\r\n```\r\n\r\n### Query Methods\r\n\r\n```csharp\r\n// Filtering\r\nQuery.Equal(\"field\", \"value\") // == (or pass array 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(new List<string> { \"field1\", \"field2\" })\r\nQuery.Or(new List<string> { Query.Equal(\"a\", 1), Query.Equal(\"b\", 2) }) // OR\r\nQuery.And(new List<string> { Query.GreaterThan(\"age\", 18), Query.LessThan(\"age\", 65) }) // AND (default)\r\n```\r\n\r\n### File Storage\r\n\r\n```csharp\r\nvar storage = new Storage(client);\r\n\r\n// Upload file\r\nvar file = await storage.CreateFile(\"[BUCKET_ID]\", ID.Unique(), InputFile.FromPath(\"/path/to/file.png\"));\r\n\r\n// List files\r\nvar files = await storage.ListFiles(\"[BUCKET_ID]\");\r\n\r\n// Delete file\r\nawait storage.DeleteFile(\"[BUCKET_ID]\", \"[FILE_ID]\");\r\n```\r\n\r\n#### InputFile Factory Methods\r\n\r\n```csharp\r\nusing Appwrite.Models;\r\n\r\nInputFile.FromPath(\"/path/to/file.png\") // from filesystem path\r\nInputFile.FromBytes(byteArray, \"file.png\", \"image/png\") // from byte[]\r\nInputFile.FromStream(stream, \"file.png\", \"image/png\", size) // from Stream (size required)\r\n```\r\n\r\n### Teams\r\n\r\n```csharp\r\nvar teams = new Teams(client);\r\n\r\n// Create team\r\nvar team = await teams.Create(ID.Unique(), \"Engineering\");\r\n\r\n// List teams\r\nvar list = await teams.List();\r\n\r\n// Create membership (invite user by email)\r\nvar membership = await teams.CreateMembership(\r\n teamId: \"[TEAM_ID]\",\r\n roles: new List<string> { \"editor\" },\r\n email: \"user@example.com\"\r\n);\r\n\r\n// List memberships\r\nvar members = await teams.ListMemberships(\"[TEAM_ID]\");\r\n\r\n// Update membership roles\r\nawait teams.UpdateMembership(\"[TEAM_ID]\", \"[MEMBERSHIP_ID]\", new List<string> { \"admin\" });\r\n\r\n// Delete team\r\nawait teams.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```csharp\r\nvar functions = new Functions(client);\r\n\r\n// Execute function\r\nvar execution = await functions.CreateExecution(\"[FUNCTION_ID]\", body: \"{\\\"key\\\": \\\"value\\\"}\");\r\n\r\n// List executions\r\nvar executions = await functions.ListExecutions(\"[FUNCTION_ID]\");\r\n```\r\n\r\n#### Writing a Function Handler (.NET runtime)\r\n\r\n```csharp\r\n// src/Main.cs — Appwrite Function entry point\r\nusing System.Text.Json;\r\n\r\npublic async Task<RuntimeOutput> Main(RuntimeContext context)\r\n{\r\n // context.Req.Body — raw body (string)\r\n // context.Req.BodyJson — parsed JSON (JsonElement)\r\n // context.Req.Headers — headers (Dictionary)\r\n // context.Req.Method — HTTP method\r\n // context.Req.Path — URL path\r\n // context.Req.Query — query params (Dictionary)\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(new { message = \"Hello from Appwrite Function!\" });\r\n\r\n return context.Res.Json(new { success = true }); // JSON\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 .NET frameworks (ASP.NET, Blazor Server, 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```csharp\r\nusing Appwrite;\r\nusing Appwrite.Services;\r\n\r\n// Admin client (reusable)\r\nvar adminClient = new Client()\r\n .SetEndpoint(\"https://<REGION>.cloud.appwrite.io/v1\")\r\n .SetProject(\"[PROJECT_ID]\")\r\n .SetKey(Environment.GetEnvironmentVariable(\"APPWRITE_API_KEY\"));\r\n\r\n// Session client (create per-request)\r\nvar sessionClient = new Client()\r\n .SetEndpoint(\"https://<REGION>.cloud.appwrite.io/v1\")\r\n .SetProject(\"[PROJECT_ID]\");\r\n\r\nvar session = Request.Cookies[\"a_session_[PROJECT_ID]\"];\r\nif (session != null)\r\n{\r\n sessionClient.SetSession(session);\r\n}\r\n```\r\n\r\n#### Email/Password Login (ASP.NET Minimal API)\r\n\r\n```csharp\r\napp.MapPost(\"/login\", async (HttpContext ctx, LoginRequest body) =>\r\n{\r\n var account = new Account(adminClient);\r\n var session = await account.CreateEmailPasswordSession(body.Email, body.Password);\r\n\r\n // Cookie name must be a_session_<PROJECT_ID>\r\n ctx.Response.Cookies.Append(\"a_session_[PROJECT_ID]\", session.Secret, new CookieOptions\r\n {\r\n HttpOnly = true,\r\n Secure = true,\r\n SameSite = SameSiteMode.Strict,\r\n Path = \"/\",\r\n });\r\n\r\n return Results.Ok(new { success = true });\r\n});\r\n```\r\n\r\n#### Authenticated Requests\r\n\r\n```csharp\r\napp.MapGet(\"/user\", async (HttpContext ctx) =>\r\n{\r\n var session = ctx.Request.Cookies[\"a_session_[PROJECT_ID]\"];\r\n if (session == null) return Results.Unauthorized();\r\n\r\n var sessionClient = new Client()\r\n .SetEndpoint(\"https://<REGION>.cloud.appwrite.io/v1\")\r\n .SetProject(\"[PROJECT_ID]\")\r\n .SetSession(session);\r\n\r\n var account = new Account(sessionClient);\r\n var user = await account.Get();\r\n return Results.Ok(user);\r\n});\r\n```\r\n\r\n#### OAuth2 SSR Flow\r\n\r\n```csharp\r\n// Step 1: Redirect to OAuth provider\r\napp.MapGet(\"/oauth\", async () =>\r\n{\r\n var account = new Account(adminClient);\r\n var redirectUrl = await account.CreateOAuth2Token(\r\n provider: OAuthProvider.Github,\r\n success: \"https://example.com/oauth/success\",\r\n failure: \"https://example.com/oauth/failure\"\r\n );\r\n return Results.Redirect(redirectUrl);\r\n});\r\n\r\n// Step 2: Handle callback — exchange token for session\r\napp.MapGet(\"/oauth/success\", async (HttpContext ctx, string userId, string secret) =>\r\n{\r\n var account = new Account(adminClient);\r\n var session = await account.CreateSession(userId, secret);\r\n\r\n ctx.Response.Cookies.Append(\"a_session_[PROJECT_ID]\", session.Secret, new CookieOptions\r\n {\r\n HttpOnly = true, Secure = true, SameSite = SameSiteMode.Strict, Path = \"/\",\r\n });\r\n\r\n return Results.Ok(new { success = true });\r\n});\r\n```\r\n\r\n> **Cookie security:** Always use `HttpOnly`, `Secure`, and `SameSite = SameSiteMode.Strict` to prevent XSS. The cookie name must be `a_session_<PROJECT_ID>`.\r\n\r\n> **Forwarding user agent:** Call `sessionClient.SetForwardedUserAgent(ctx.Request.Headers[\"User-Agent\"])` to record the end-user's browser info for debugging and security.\r\n\r\n## Error Handling\r\n\r\n```csharp\r\nusing Appwrite;\r\n\r\ntry\r\n{\r\n var row = await tablesDB.GetRow(\"[DATABASE_ID]\", \"[TABLE_ID]\", \"[ROW_ID]\");\r\n}\r\ncatch (AppwriteException e)\r\n{\r\n Console.WriteLine(e.Message); // human-readable message\r\n Console.WriteLine(e.Code); // HTTP status code (int)\r\n Console.WriteLine(e.Type); // error type (e.g. \"document_not_found\")\r\n Console.WriteLine(e.Response); // full response body\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```csharp\r\nusing Appwrite;\r\n// Permission and Role are included in the main namespace\r\n```\r\n\r\n### Database Row with Permissions\r\n\r\n```csharp\r\nvar doc = await tablesDB.CreateRow(\"[DATABASE_ID]\", \"[TABLE_ID]\", ID.Unique(),\r\n new Dictionary<string, object> { { \"title\", \"Hello World\" } },\r\n new List<string>\r\n {\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### File Upload with Permissions\r\n\r\n```csharp\r\nvar file = await storage.CreateFile(\"[BUCKET_ID]\", ID.Unique(),\r\n InputFile.FromPath(\"/path/to/file.png\"),\r\n new List<string>\r\n {\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> **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: 5ac11bca4c853a9f77e33fbca12ce8ff66b8613951f31df776defcb8bbdab7b1