{"id":13616,"plugin_id":"plugin_asdk_app_6aa2c33323108191b17b9ccf4233b3ce","kind":"skill","collection_source":null,"comparison_source":null,"observed_at":"2026-09-30T23:07:50.145Z","digest":"1d280c59335d33a3837c29ee10d7d64fcc0188c7519d6c599675f774b47db36d","against":null,"payload":{"name":"appwrite-dart","description":"Appwrite Dart SDK skill. Use when building Flutter apps (mobile, web, desktop) or server-side Dart applications with Appwrite. Covers client-side auth (email, OAuth), database queries, file uploads with native file handling, real-time subscriptions, and server-side admin via API keys for user management, database administration, storage, and functions.","included_files":[],"skill_md_contents":"---\r\nname: appwrite-dart\r\ndescription: Appwrite Dart SDK skill. Use when building Flutter apps (mobile, web, desktop) or server-side Dart applications with Appwrite. Covers client-side auth (email, OAuth), database queries, file uploads with native file handling, real-time subscriptions, and server-side admin via API keys for user management, database administration, storage, and functions.\r\n---\r\n\r\n\r\n# Appwrite Dart SDK\r\n\r\n## Installation\r\n\r\n```bash\r\n# Flutter (client-side)\r\nflutter pub add appwrite\r\n\r\n# Dart (server-side)\r\ndart pub add dart_appwrite\r\n```\r\n\r\n## Setting Up the Client\r\n\r\n### Client-side (Flutter)\r\n\r\n```dart\r\nimport 'package:appwrite/appwrite.dart';\r\n\r\nfinal client = Client()\r\n    .setEndpoint('https://<REGION>.cloud.appwrite.io/v1')\r\n    .setProject('[PROJECT_ID]');\r\n```\r\n\r\n### Server-side (Dart)\r\n\r\n```dart\r\nimport 'package:dart_appwrite/dart_appwrite.dart';\r\n\r\nfinal client = Client()\r\n    .setEndpoint('https://<REGION>.cloud.appwrite.io/v1')\r\n    .setProject(Platform.environment['APPWRITE_PROJECT_ID']!)\r\n    .setKey(Platform.environment['APPWRITE_API_KEY']!);\r\n```\r\n\r\n## Code Examples\r\n\r\n### Authentication (client-side)\r\n\r\n```dart\r\nfinal account = Account(client);\r\n\r\n// Signup\r\nawait account.create(userId: ID.unique(), email: 'user@example.com', password: 'password123', name: 'User Name');\r\n\r\n// Login\r\nfinal session = await account.createEmailPasswordSession(email: 'user@example.com', password: 'password123');\r\n\r\n// OAuth login\r\nawait account.createOAuth2Session(provider: OAuthProvider.google);\r\n\r\n// Get current user\r\nfinal user = await account.get();\r\n\r\n// Logout\r\nawait account.deleteSession(sessionId: 'current');\r\n```\r\n\r\n### User Management (server-side)\r\n\r\n```dart\r\nfinal users = Users(client);\r\n\r\n// Create user\r\nfinal user = await users.create(userId: ID.unique(), email: 'user@example.com', password: 'password123', name: 'User Name');\r\n\r\n// List users\r\nfinal list = await users.list(queries: [Query.limit(25)]);\r\n\r\n// Get user\r\nfinal fetched = await users.get(userId: '[USER_ID]');\r\n\r\n// Delete user\r\nawait users.delete(userId: '[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 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.\r\n\r\n```dart\r\nfinal tablesDB = TablesDB(client);\r\n\r\n// Create database (server-side only)\r\nfinal db = await tablesDB.create(databaseId: ID.unique(), name: 'My Database');\r\n\r\n// Create table (server-side only)\r\nfinal col = await tablesDB.createTable(databaseId: '[DATABASE_ID]', tableId: ID.unique(), name: 'My Table');\r\n\r\n// Create row\r\nfinal doc = await tablesDB.createRow(\r\n    databaseId: '[DATABASE_ID]',\r\n    tableId: '[TABLE_ID]',\r\n    rowId: ID.unique(),\r\n    data: {'title': 'Hello', 'done': false},\r\n);\r\n\r\n// Query rows\r\nfinal results = await tablesDB.listRows(\r\n    databaseId: '[DATABASE_ID]',\r\n    tableId: '[TABLE_ID]',\r\n    queries: [Query.equal('done', false), Query.limit(10)],\r\n);\r\n\r\n// Get row\r\nfinal row = await tablesDB.getRow(databaseId: '[DATABASE_ID]', tableId: '[TABLE_ID]', rowId: '[ROW_ID]');\r\n\r\n// Update row\r\nawait tablesDB.updateRow(\r\n    databaseId: '[DATABASE_ID]',\r\n    tableId: '[TABLE_ID]',\r\n    rowId: '[ROW_ID]',\r\n    data: {'done': true},\r\n);\r\n\r\n// Delete row\r\nawait tablesDB.deleteRow(\r\n    databaseId: '[DATABASE_ID]',\r\n    tableId: '[TABLE_ID]',\r\n    rowId: '[ROW_ID]',\r\n);\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```dart\r\n// Create table with explicit string column types\r\nawait tablesDB.createTable(\r\n    databaseId: '[DATABASE_ID]',\r\n    tableId: ID.unique(),\r\n    name: 'articles',\r\n    columns: [\r\n        {'key': 'title',    'type': 'varchar',    'size': 255, 'required': true},   // inline, fully indexable\r\n        {'key': 'summary',  'type': 'text',                    'required': false},  // off-page, prefix index only\r\n        {'key': 'body',     'type': 'mediumtext',              'required': false},  // up to ~4 M chars\r\n        {'key': 'raw_data', 'type': 'longtext',                'required': false},  // up to ~1 B chars\r\n    ],\r\n);\r\n```\r\n\r\n### Query Methods\r\n\r\n```dart\r\n// Filtering\r\nQuery.equal('field', 'value')             // == (or pass list 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(['field1', 'field2'])        // return only specified fields\r\nQuery.or([Query.equal('a', 1), Query.equal('b', 2)])   // OR\r\nQuery.and([Query.greaterThan('age', 18), Query.lessThan('age', 65)])  // AND (default)\r\n```\r\n\r\n### File Storage\r\n\r\n```dart\r\nfinal storage = Storage(client);\r\n\r\n// Upload file\r\nfinal file = await storage.createFile(\r\n    bucketId: '[BUCKET_ID]',\r\n    fileId: ID.unique(),\r\n    file: InputFile.fromPath(path: '/path/to/file.png', filename: 'file.png'),\r\n);\r\n\r\n// Get file preview\r\nfinal preview = storage.getFilePreview(bucketId: '[BUCKET_ID]', fileId: '[FILE_ID]', width: 300, height: 300);\r\n\r\n// List files\r\nfinal files = await storage.listFiles(bucketId: '[BUCKET_ID]');\r\n\r\n// Delete file\r\nawait storage.deleteFile(bucketId: '[BUCKET_ID]', fileId: '[FILE_ID]');\r\n```\r\n\r\n#### InputFile Factory Methods\r\n\r\n```dart\r\n// Client-side (Flutter)\r\nInputFile.fromPath(path: '/path/to/file.png', filename: 'file.png')    // from path\r\nInputFile.fromBytes(bytes: uint8List, filename: 'file.png')            // from Uint8List\r\n\r\n// Server-side (Dart)\r\nInputFile.fromPath(path: '/path/to/file.png', filename: 'file.png')\r\nInputFile.fromBytes(bytes: uint8List, filename: 'file.png')\r\n```\r\n\r\n### Teams\r\n\r\n```dart\r\nfinal teams = Teams(client);\r\n\r\n// Create team\r\nfinal team = await teams.create(teamId: ID.unique(), name: 'Engineering');\r\n\r\n// List teams\r\nfinal list = await teams.list();\r\n\r\n// Create membership (invite user by email)\r\nfinal membership = await teams.createMembership(\r\n    teamId: '[TEAM_ID]',\r\n    roles: ['editor'],\r\n    email: 'user@example.com',\r\n);\r\n\r\n// List memberships\r\nfinal members = await teams.listMemberships(teamId: '[TEAM_ID]');\r\n\r\n// Update membership roles\r\nawait teams.updateMembership(teamId: '[TEAM_ID]', membershipId: '[MEMBERSHIP_ID]', roles: ['admin']);\r\n\r\n// Delete team\r\nawait teams.delete(teamId: '[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### Real-time Subscriptions (client-side)\r\n\r\n```dart\r\nfinal realtime = Realtime(client);\r\n\r\n// Subscribe to row changes\r\nfinal subscription = realtime.subscribe([\r\n    Channel.tablesdb('[DATABASE_ID]').table('[TABLE_ID]').row(),\r\n]);\r\nsubscription.stream.listen((response) {\r\n    print(response.events);   // e.g. ['tablesdb.*.tables.*.rows.*.create']\r\n    print(response.payload);  // the affected resource\r\n});\r\n\r\n// Subscribe to multiple channels\r\nfinal multi = realtime.subscribe([\r\n    Channel.tablesdb('[DATABASE_ID]').table('[TABLE_ID]').row(),\r\n    Channel.bucket('[BUCKET_ID]').file(),\r\n]);\r\n\r\n// Cleanup\r\nsubscription.close();\r\n```\r\n\r\n**Available channels:**\r\n\r\n| Channel | Description |\r\n|---------|-------------|\r\n| `account` | Changes to the authenticated user's account |\r\n| `tablesdb.[DB_ID].tables.[TABLE_ID].rows` | All rows in a table |\r\n| `tablesdb.[DB_ID].tables.[TABLE_ID].rows.[ROW_ID]` | A specific row |\r\n| `buckets.[BUCKET_ID].files` | All files in a bucket |\r\n| `buckets.[BUCKET_ID].files.[FILE_ID]` | A specific file |\r\n| `teams` | Changes to teams the user belongs to |\r\n| `teams.[TEAM_ID]` | A specific team |\r\n| `memberships` | The user's team memberships |\r\n| `memberships.[MEMBERSHIP_ID]` | A specific membership |\r\n| `functions.[FUNCTION_ID].executions` | Function execution updates |\r\n\r\nResponse fields: `events` (array), `payload` (resource), `channels` (matched), `timestamp` (ISO 8601).\r\n\r\n### Serverless Functions (server-side)\r\n\r\n```dart\r\nfinal functions = Functions(client);\r\n\r\n// Execute function\r\nfinal execution = await functions.createExecution(functionId: '[FUNCTION_ID]', body: '{\"key\": \"value\"}');\r\n\r\n// List executions\r\nfinal executions = await functions.listExecutions(functionId: '[FUNCTION_ID]');\r\n```\r\n\r\n#### Writing a Function Handler (Dart runtime)\r\n\r\n```dart\r\n// lib/main.dart — Appwrite Function entry point\r\nFuture<dynamic> main(final context) async {\r\n    // context.req.body        — raw body (String)\r\n    // context.req.bodyJson    — parsed JSON (Map or null)\r\n    // context.req.headers     — headers (Map)\r\n    // context.req.method      — HTTP method\r\n    // context.req.path        — URL path\r\n    // context.req.query       — query params (Map)\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({'message': 'Hello from Appwrite Function!'});\r\n    }\r\n\r\n    return context.res.json({'success': true});      // JSON\r\n    // return context.res.text('Hello');              // plain text\r\n    // return context.res.empty();                    // 204\r\n    // return context.res.redirect('https://...');    // 302\r\n}\r\n```\r\n\r\n### Server-Side Rendering (SSR) Authentication\r\n\r\nSSR apps using server-side Dart (Dart Frog, Shelf, etc.) use the **server SDK** (`dart_appwrite`) 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```dart\r\nimport 'package:dart_appwrite/dart_appwrite.dart';\r\n\r\n// Admin client (reusable)\r\nfinal adminClient = Client()\r\n    .setEndpoint('https://<REGION>.cloud.appwrite.io/v1')\r\n    .setProject('[PROJECT_ID]')\r\n    .setKey(Platform.environment['APPWRITE_API_KEY']!);\r\n\r\n// Session client (create per-request)\r\nfinal sessionClient = Client()\r\n    .setEndpoint('https://<REGION>.cloud.appwrite.io/v1')\r\n    .setProject('[PROJECT_ID]');\r\n\r\nfinal session = request.cookies['a_session_[PROJECT_ID]'];\r\nif (session != null) {\r\n    sessionClient.setSession(session);\r\n}\r\n```\r\n\r\n#### Email/Password Login\r\n\r\n```dart\r\nfinal account = Account(adminClient);\r\nfinal session = await account.createEmailPasswordSession(\r\n    email: body['email'],\r\n    password: body['password'],\r\n);\r\n\r\n// Cookie name must be a_session_<PROJECT_ID>\r\nresponse.headers.add('Set-Cookie',\r\n    'a_session_[PROJECT_ID]=${session.secret}; '\r\n    'HttpOnly; Secure; SameSite=Strict; '\r\n    'Expires=${HttpDate.format(DateTime.parse(session.expire))}; Path=/');\r\n```\r\n\r\n#### Authenticated Requests\r\n\r\n```dart\r\nfinal session = request.cookies['a_session_[PROJECT_ID]'];\r\nif (session == null) {\r\n    return Response(statusCode: 401, body: 'Unauthorized');\r\n}\r\n\r\nfinal sessionClient = Client()\r\n    .setEndpoint('https://<REGION>.cloud.appwrite.io/v1')\r\n    .setProject('[PROJECT_ID]')\r\n    .setSession(session);\r\n\r\nfinal account = Account(sessionClient);\r\nfinal user = await account.get();\r\n```\r\n\r\n#### OAuth2 SSR Flow\r\n\r\n```dart\r\n// Step 1: Redirect to OAuth provider\r\nfinal account = Account(adminClient);\r\nfinal 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\nreturn Response(statusCode: 302, headers: {'Location': redirectUrl});\r\n\r\n// Step 2: Handle callback — exchange token for session\r\nfinal account = Account(adminClient);\r\nfinal session = await account.createSession(\r\n    userId: request.uri.queryParameters['userId']!,\r\n    secret: request.uri.queryParameters['secret']!,\r\n);\r\n// Set session cookie as above\r\n```\r\n\r\n> **Cookie security:** Always use `HttpOnly`, `Secure`, and `SameSite=Strict` to prevent XSS. The cookie name must be `a_session_<PROJECT_ID>`.\r\n\r\n> **Forwarding user agent:** Call `sessionClient.setForwardedUserAgent(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```dart\r\nimport 'package:appwrite/appwrite.dart';\r\n// AppwriteException is included in the main import\r\n\r\ntry {\r\n    final row = await tablesDB.getRow(databaseId: '[DATABASE_ID]', tableId: '[TABLE_ID]', rowId: '[ROW_ID]');\r\n} on AppwriteException catch (e) {\r\n    print(e.message);    // human-readable message\r\n    print(e.code);       // HTTP status code (int)\r\n    print(e.type);       // error type (e.g. 'document_not_found')\r\n    print(e.response);   // full response body (Map)\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```dart\r\nimport 'package:appwrite/appwrite.dart';\r\n// Permission and Role are included in the main package import\r\n```\r\n\r\n### Database Row with Permissions\r\n\r\n```dart\r\nfinal doc = await tablesDB.createRow(\r\n    databaseId: '[DATABASE_ID]',\r\n    tableId: '[TABLE_ID]',\r\n    rowId: ID.unique(),\r\n    data: {'title': 'Hello World'},\r\n    permissions: [\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```dart\r\nfinal file = await storage.createFile(\r\n    bucketId: '[BUCKET_ID]',\r\n    fileId: ID.unique(),\r\n    file: InputFile.fromPath(path: '/path/to/file.png', filename: 'file.png'),\r\n    permissions: [\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"},"changes":[],"summary":"First saved snapshot. No earlier version is available for comparison.","summary_kind":"deterministic","summary_metadata":{}}