← 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-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"
}SHA-256: 1d280c59335d33a3837c29ee10d7d64fcc0188c7519d6c599675f774b47db36d