
Dotnet
- 1 installs
- 10 repo stars
- Updated July 29, 2026
- appwrite/claude-plugin
dotnet is a Claude skill for building server-side C# and .NET applications with the Appwrite backend, including ASP.NET and Blazor.
About
dotnet is a skill for building server-side C# or .NET applications with Appwrite, including ASP.NET and Blazor integrations. It covers user management, database and table CRUD, file storage and functions authenticated via API keys. A developer uses it when writing a .NET backend that talks to Appwrite Cloud or a self-hosted instance. It uses the modern TablesDB API with named arguments and documents queries, string column types and InputFile factory methods.
- Appwrite .NET SDK for server-side C#, ASP.NET and Blazor integrations
- Covers user management, TablesDB CRUD, file storage and functions via API keys
- Documents query methods, string column types and InputFile factories
Dotnet by the numbers
- 1 all-time installs (skills.sh)
- Ranked #3,830 of 4,347 Backend & APIs skills by installs in the Skillselion catalog
- Data as of Jul 30, 2026 (Skillselion catalog sync)
dotnet capabilities & compatibility
requires an Appwrite project ID and API key; Appwrite has a free cloud tier
- Capabilities
- appwrite go · appwrite cli · api development · database
- Use cases
- api development · database
- Pricing
- Bring your own API key
What dotnet says it does
Appwrite .NET SDK skill. Use when building server-side C# or .NET applications with Appwrite, including ASP.NET and Blazor integrations.
Use `TablesDB` (not the deprecated `Databases` class) for all new code.
npx skills add https://github.com/appwrite/claude-plugin --skill dotnetAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 1 |
|---|---|
| repo stars | ★ 10 |
| Last updated | July 29, 2026 |
| Repository | appwrite/claude-plugin ↗ |
What it does
Build a server-side C#/.NET backend on Appwrite for users, TablesDB CRUD, storage and functions using API keys.
Who is it for?
writing a server-side C#/.NET backend that uses Appwrite for users, data, storage and functions
Skip if: client-side or non-.NET Appwrite work
When should I use this skill?
building a .NET, ASP.NET or Blazor backend that calls Appwrite
What you get
Working Appwrite user, TablesDB, storage and function calls from C#/.NET with correct queries.
- Appwrite .NET client setup
- TablesDB CRUD code
- file storage and function calls
By the numbers
- four documented string column types
- default query limit of 25, max 100
Files
Appwrite .NET SDK
Installation
dotnet add package AppwriteSetting Up the Client
using Appwrite;
using Appwrite.Services;
using Appwrite.Models;
var client = new Client()
.SetEndpoint("https://<REGION>.cloud.appwrite.io/v1")
.SetProject(Environment.GetEnvironmentVariable("APPWRITE_PROJECT_ID"))
.SetKey(Environment.GetEnvironmentVariable("APPWRITE_API_KEY"));Code Examples
User Management
var users = new Users(client);
// Create user
var user = await users.Create(ID.Unique(), "user@example.com", null, "password123", "User Name");
// List users
var list = await users.List(new List<string> { Query.Limit(25) });
// Get user
var fetched = await users.Get("[USER_ID]");
// Delete user
await users.Delete("[USER_ID]");Database Operations
Note: UseTablesDB(not the deprecatedDatabasesclass) for all new code. Only useDatabasesif the existing codebase already relies on it or the user explicitly requests it.
>
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.var tablesDB = new TablesDB(client);
// Create database
var db = await tablesDB.Create(ID.Unique(), "My Database");
// Create row
var doc = await tablesDB.CreateRow("[DATABASE_ID]", "[TABLE_ID]", ID.Unique(),
new Dictionary<string, object> { { "title", "Hello World" } });
// Query rows
var results = await tablesDB.ListRows("[DATABASE_ID]", "[TABLE_ID]",
new List<string> { Query.Equal("title", "Hello World"), Query.Limit(10) });
// Get row
var row = await tablesDB.GetRow("[DATABASE_ID]", "[TABLE_ID]", "[ROW_ID]");
// Update row
await tablesDB.UpdateRow("[DATABASE_ID]", "[TABLE_ID]", "[ROW_ID]",
new Dictionary<string, object> { { "title", "Updated" } });
// Delete row
await tablesDB.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 |
varcharis stored inline and counts towards the 64 KB row size limit. Prefer for short, indexed fields like names, slugs, or identifiers.text,mediumtext, andlongtextare stored off-page (only a 20-byte pointer lives in the row), so they don't consume the row size budget.sizeis not required for these types.
// Create table with explicit string column types
await tablesDB.CreateTable("[DATABASE_ID]", ID.Unique(), "articles",
new List<object> {
new { key = "title", type = "varchar", size = 255, required = true }, // inline, fully indexable
new { key = "summary", type = "text", required = false }, // off-page, prefix index only
new { key = "body", type = "mediumtext", required = false }, // up to ~4 M chars
new { key = "raw_data", type = "longtext", required = false }, // up to ~1 B chars
});Query Methods
// Filtering
Query.Equal("field", "value") // == (or pass array 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(new List<string> { "field1", "field2" })
Query.Or(new List<string> { Query.Equal("a", 1), Query.Equal("b", 2) }) // OR
Query.And(new List<string> { Query.GreaterThan("age", 18), Query.LessThan("age", 65) }) // AND (default)File Storage
var storage = new Storage(client);
// Upload file
var file = await storage.CreateFile("[BUCKET_ID]", ID.Unique(), InputFile.FromPath("/path/to/file.png"));
// List files
var files = await storage.ListFiles("[BUCKET_ID]");
// Delete file
await storage.DeleteFile("[BUCKET_ID]", "[FILE_ID]");InputFile Factory Methods
using Appwrite.Models;
InputFile.FromPath("/path/to/file.png") // from filesystem path
InputFile.FromBytes(byteArray, "file.png", "image/png") // from byte[]
InputFile.FromStream(stream, "file.png", "image/png", size) // from Stream (size required)Teams
var teams = new Teams(client);
// Create team
var team = await teams.Create(ID.Unique(), "Engineering");
// List teams
var list = await teams.List();
// Create membership (invite user by email)
var membership = await teams.CreateMembership(
teamId: "[TEAM_ID]",
roles: new List<string> { "editor" },
email: "user@example.com"
);
// List memberships
var members = await teams.ListMemberships("[TEAM_ID]");
// Update membership roles
await teams.UpdateMembership("[TEAM_ID]", "[MEMBERSHIP_ID]", new List<string> { "admin" });
// Delete team
await teams.Delete("[TEAM_ID]");Role-based access: UseRole.Team("[TEAM_ID]")for all team members orRole.Team("[TEAM_ID]", "editor")for a specific team role when setting permissions.
Serverless Functions
var functions = new Functions(client);
// Execute function
var execution = await functions.CreateExecution("[FUNCTION_ID]", body: "{\"key\": \"value\"}");
// List executions
var executions = await functions.ListExecutions("[FUNCTION_ID]");Writing a Function Handler (.NET runtime)
// src/Main.cs — Appwrite Function entry point
using System.Text.Json;
public async Task<RuntimeOutput> Main(RuntimeContext context)
{
// context.Req.Body — raw body (string)
// context.Req.BodyJson — parsed JSON (JsonElement)
// context.Req.Headers — headers (Dictionary)
// context.Req.Method — HTTP method
// context.Req.Path — URL path
// context.Req.Query — query params (Dictionary)
context.Log($"Processing: {context.Req.Method} {context.Req.Path}");
if (context.Req.Method == "GET")
return context.Res.Json(new { message = "Hello from Appwrite Function!" });
return context.Res.Json(new { 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 .NET frameworks (ASP.NET, Blazor Server, 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)
using Appwrite;
using Appwrite.Services;
// Admin client (reusable)
var adminClient = new Client()
.SetEndpoint("https://<REGION>.cloud.appwrite.io/v1")
.SetProject("[PROJECT_ID]")
.SetKey(Environment.GetEnvironmentVariable("APPWRITE_API_KEY"));
// Session client (create per-request)
var sessionClient = new Client()
.SetEndpoint("https://<REGION>.cloud.appwrite.io/v1")
.SetProject("[PROJECT_ID]");
var session = Request.Cookies["a_session_[PROJECT_ID]"];
if (session != null)
{
sessionClient.SetSession(session);
}Email/Password Login (ASP.NET Minimal API)
app.MapPost("/login", async (HttpContext ctx, LoginRequest body) =>
{
var account = new Account(adminClient);
var session = await account.CreateEmailPasswordSession(body.Email, body.Password);
// Cookie name must be a_session_<PROJECT_ID>
ctx.Response.Cookies.Append("a_session_[PROJECT_ID]", session.Secret, new CookieOptions
{
HttpOnly = true,
Secure = true,
SameSite = SameSiteMode.Strict,
Path = "/",
});
return Results.Ok(new { success = true });
});Authenticated Requests
app.MapGet("/user", async (HttpContext ctx) =>
{
var session = ctx.Request.Cookies["a_session_[PROJECT_ID]"];
if (session == null) return Results.Unauthorized();
var sessionClient = new Client()
.SetEndpoint("https://<REGION>.cloud.appwrite.io/v1")
.SetProject("[PROJECT_ID]")
.SetSession(session);
var account = new Account(sessionClient);
var user = await account.Get();
return Results.Ok(user);
});OAuth2 SSR Flow
// Step 1: Redirect to OAuth provider
app.MapGet("/oauth", async () =>
{
var account = new Account(adminClient);
var redirectUrl = await account.CreateOAuth2Token(
provider: OAuthProvider.Github,
success: "https://example.com/oauth/success",
failure: "https://example.com/oauth/failure"
);
return Results.Redirect(redirectUrl);
});
// Step 2: Handle callback — exchange token for session
app.MapGet("/oauth/success", async (HttpContext ctx, string userId, string secret) =>
{
var account = new Account(adminClient);
var session = await account.CreateSession(userId, secret);
ctx.Response.Cookies.Append("a_session_[PROJECT_ID]", session.Secret, new CookieOptions
{
HttpOnly = true, Secure = true, SameSite = SameSiteMode.Strict, Path = "/",
});
return Results.Ok(new { success = true });
});Cookie security: Always useHttpOnly,Secure, andSameSite = SameSiteMode.Strictto prevent XSS. The cookie name must bea_session_<PROJECT_ID>.
Forwarding user agent: Call sessionClient.SetForwardedUserAgent(ctx.Request.Headers["User-Agent"]) to record the end-user's browser info for debugging and security.Error Handling
using Appwrite;
try
{
var row = await tablesDB.GetRow("[DATABASE_ID]", "[TABLE_ID]", "[ROW_ID]");
}
catch (AppwriteException e)
{
Console.WriteLine(e.Message); // human-readable message
Console.WriteLine(e.Code); // HTTP status code (int)
Console.WriteLine(e.Type); // error type (e.g. "document_not_found")
Console.WriteLine(e.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 document/file level or inherited from the collection/bucket settings. Permissions are arrays of strings built with the Permission and Role helpers.
using Appwrite;
// Permission and Role are included in the main namespaceDatabase Row with Permissions
var doc = await tablesDB.CreateRow("[DATABASE_ID]", "[TABLE_ID]", ID.Unique(),
new Dictionary<string, object> { { "title", "Hello World" } },
new List<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
var file = await storage.CreateFile("[BUCKET_ID]", ID.Unique(),
InputFile.FromPath("/path/to/file.png"),
new List<string>
{
Permission.Read(Role.Any()),
Permission.Update(Role.User("[USER_ID]")),
Permission.Delete(Role.User("[USER_ID]")),
});When to set permissions: Set document/file-level permissions when you need per-resource access control. If all documents in a collection share the same rules, configure permissions at the collection/bucket level and leave document 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
Related skills
FAQ
How do I install it?
Via dotnet add package Appwrite, then configure a Client with endpoint, project and API key.
Databases or TablesDB?
Use TablesDB for all new code; only use the deprecated Databases class if the existing codebase already relies on it.