Communitygithub.com

sgaabdu4/appwrite-backend

Appwrite BaaS skill for AI agents. Covers TablesDB, Auth, Storage, Functions, Messaging, and Realtime in Dart, Python, and TypeScript.

¿Qué es appwrite-backend?

appwrite-backend is a Cursor agent skill that appwrite BaaS skill for AI agents. Covers TablesDB, Auth, Storage, Functions, Messaging, and Realtime in Dart, Python, and TypeScript.

Compatible con~Claude Code~Codex CLICursor
npx skills add sgaabdu4/appwrite-backend

Preguntar en tu IA favorita

Abre un nuevo chat con esta habilidad de agente ya precargada.

Documentación

Appwrite Development

Critical Rules

  1. Use official SDK packages only — Dart/Flutter/TypeScript/Python must use sdk-routing. Raw REST/GraphQL HTTP via fetch, requests, dio, package:http, curl, etc. is a violation unless the SDK lacks the endpoint or an isolated, tested Client.call works around SDK model parsing.
  2. Pin SDKs by target — Cloud: latest stable SDK. Self-hosted 1.9.x: dart_appwrite 25.1.0, Flutter appwrite 25.2.0, node-appwrite 26.2.0, web appwrite 26.1.0, Python appwrite 21.0.0, CLI 22.4.0.
  3. Use TablesDB API — Collections API deprecated 1.8.0
  4. Use ID.unique() for all unique IDs — Row IDs (rowId:), file IDs, user IDs, team IDs, webhook IDs, message IDs, subscriber IDs, and entity IDs in columns. No hardcoded unique IDs, custom generators, names, timestamps, or slugs-as-IDs; they overflow column limits and leak data. Use stable natural keys only as indexed columns.
  5. Use Query.select() — Relationships return IDs only without explicit selection.
  6. Use cursor pagination — Offset degrades on large tables
  7. Use Operator for counters — Avoids race conditions
  8. Create indexes — Queries without scan entire tables
  9. Init outside handler — SDK/connections persist between warm invocations
  10. Group functions by domain — One per domain, not per op
  11. Event triggers over polling — One trigger replaces thousands of requests
  12. Use explicit string typesstring deprecated; use varchar or text/mediumtext/longtext
  13. Use appwrite generate — Type-safe SDK from schema
  14. Use Channel helpers — Type-safe realtime subs, not raw strings
  15. Use Realtime queries — Server-side event filtering, not client-side
  16. Async-start long-running Functions — Client createExecution calls for delete/sync/import/export/migrate/generate flows use async execution, then reconcile source-of-truth state with bounded polling/realtime/fetch. Do not block on backend completion; report destructive failures only after reconciliation proves the entity/account still exists.

CLI Quick Check (Top)

Use a repo-local ignored .env.appwrite.local per project; do not trust global CLI config.

# .env.appwrite.local (gitignored)
APPWRITE_ENDPOINT=https://<endpoint>/v1
APPWRITE_PROJECT_ID=<project_id>
APPWRITE_API_KEY=standard_...
# Use "cloud" or the self-hosted server line.
APPWRITE_SERVER_VERSION=cloud

CLI version policy:

Before Appwrite CLI work:

set -a
[ -f .env.appwrite.local ] && . ./.env.appwrite.local
set +a

case "$APPWRITE_SERVER_VERSION" in
  cloud|"")
    npm install -g appwrite-cli@latest
    ;;
  1.9|1.9.*)
    npm install -g [email protected]
    ;;
  *)
    echo "Unsupported APPWRITE_SERVER_VERSION=$APPWRITE_SERVER_VERSION; choose a matching CLI before continuing."
    exit 1
    ;;
esac

appwrite --version
appwrite client \
  --endpoint "$APPWRITE_ENDPOINT" \
  --project-id "$APPWRITE_PROJECT_ID" \
  --key "$APPWRITE_API_KEY"

appwrite client --debug

appwrite client --debug must show the expected endpoint/project and a masked key before proceeding. If missing, ask for endpoint, project ID, API key, and server version.

Rules: appwrite.config.json = local project config. appwrite client ... = global override (non-interactive). Clear override: appwrite client --reset. CLI helper flags vary by version; if unavailable, use raw --queries or parse plain table output for quick status checks. Details: appwrite-cli

Terminology (1.8.0+)

OldNew
CollectionsTables
DocumentsRows
AttributesColumns
DatabasesTablesDB

Setup

Package policy:

  • Cloud: latest stable official SDK.
  • Self-hosted 1.9.x: use Critical Rule 2 pins.
  • TypeScript/React browser: appwrite; TypeScript server/SSR/Functions: node-appwrite.
  • Python: appwrite; prefer keyword arguments for SDK calls.
  • Dart: appwrite for Flutter/client apps, dart_appwrite for server/Functions; prefer named parameters.
import 'package:dart_appwrite/dart_appwrite.dart';

final client = Client()
    .setEndpoint('https://cloud.appwrite.io/v1')
    .setProject('<PROJECT_ID>')
    .setKey('<API_KEY>');

final tablesDB = TablesDB(client);
from appwrite.client import Client
from appwrite.services.tables_db import TablesDB
client = Client()
client.set_endpoint('https://cloud.appwrite.io/v1')
client.set_project('<PROJECT_ID>')
client.set_key('<API_KEY>')
tables_db = TablesDB(client)
import { Client, TablesDB } from 'node-appwrite';
const client = new Client()
    .setEndpoint('https://cloud.appwrite.io/v1')
    .setProject('<PROJECT_ID>')
    .setKey('<API_KEY>');
const tablesDB = new TablesDB(client);

TablesDB CRUD

// Create
await tablesDB.createRow(databaseId: 'db', tableId: 'users', rowId: ID.unique(),
    data: {'name': 'Alice'});

// Read
final rows = await tablesDB.listRows(databaseId: 'db', tableId: 'users',
    queries: [Query.equal('status', 'active'), Query.select(['name', 'email'])]);

// Update
await tablesDB.updateRow(databaseId: 'db', tableId: 'users', rowId: 'user_123',
    data: {'status': 'inactive'});

// Upsert
await tablesDB.upsertRow(databaseId: 'db', tableId: 'settings', rowId: 'prefs',
    data: {'theme': 'dark'});

// Delete
await tablesDB.deleteRow(databaseId: 'db', tableId: 'users', rowId: 'user_123');

Use SDK idioms:

  • TypeScript uses object parameters: tablesDB.createRow({ databaseId, tableId, rowId, data }).
  • Python uses keyword arguments: tables_db.create_row(database_id='db', table_id='users', row_id=ID.unique(), data={...}).
  • Dart uses named parameters as shown above.

Bulk: bulk-operations.md | Chunked ID queries: chunked-queries.md


Query Reference

Comparison: equal | notEqual | lessThan | lessThanEqual | greaterThan | greaterThanEqual | between | notBetween String: startsWith | endsWith | contains | search (+ not variants) Null: isNull | isNotNull · Logical: and([...]) | or([...]) Pagination: select | limit | cursorAfter | cursorBefore | orderAsc | orderDesc | orderRandom Timestamp: createdAfter | createdBefore | updatedAfter | updatedBefore Spatial: distanceEqual | distanceLessThan | distanceGreaterThan | intersects | overlaps | touches | crosses (+ not variants)

All prefixed Query.. Details: query-optimization.md


Operators (Atomic Updates)

data: {
    'likes': Operator.increment(1),
    'tags': Operator.arrayAppend(['trending']),
    'updatedAt': Operator.dateSetNow(),
}

Numeric: increment | decrement | multiply | divide Array: arrayAppend | arrayPrepend | arrayRemove | arrayUnique | arrayIntersect | arrayDiff Other: toggle | stringConcat | stringReplace | dateAddDays | dateSetNow

Details: atomic-operators.md


Column Types

TypeMax CharsIndexingUse
varchar16,383Full (if size < 768)Queryable short strings
text16,383Prefix onlyDescriptions, notes
mediumtext4,194,303Prefix onlyArticles
longtext1,073,741,823Prefix onlyLarge documents

string deprecated. Use varchar for queryable, text for non-indexed.

Other: integer | float | boolean | datetime | email | url | ip | enum | relationship | point | line | polygon

Details: schema-management.md


Performance

RuleImpact
Cursor pagination10-100x faster than offset
Pagination mixin (Dart)~50 lines saved per datasource
Query.select()12-18x faster for relationships
total: falseEliminates COUNT scan
Indexes100x faster on large tables
OperatorsNo race conditions
Bulk operationsN → 1 request
Delta syncFetches only changed rows

Details: performance.md, pagination-performance.md


Type-Safe SDK Generation

appwrite generate

Gen typed helpers from schema into generated/appwrite/. Autocomplete + compile checks. Regen after schema change. CLI flow: login -> init project -> pull -> generate -> push. Details: appwrite-cli


Authentication

Email/password, OAuth (50+ providers), phone, magic link, anon, email OTP, custom token. MFA: TOTP/email/phone/recovery. SSR sessions. JWT for functions. SSR cookie: a_session_<PROJECT_ID>. Admin client creates session. Per-request session client reads user context. Email policies can block free, aliased, or disposable emails at signup/update.

Details: authentication.md | auth-methods.md


Storage

Upload/download/preview w/ transforms (resize, format conversion). File tokens for shareable URLs. HEIC, AVIF, WebP supported. SDKs handle chunking/parallel chunk uploads; do not hand-roll upload HTTP.

Details: storage-files.md


Realtime

final sub = realtime.subscribe(['tablesdb.db.tables.posts.rows']);
sub.stream.listen((e) => print(e.events));

Channels: account | tablesdb.<DB>.tables.<TABLE>.rows | buckets.<BUCKET>.files | presences

Channel helpers (preferred): Channel class for type-safe subs w/ IDE autocomplete:

import { Client, Realtime, Channel, Query } from "appwrite";
const sub = await realtime.subscribe(
    Channel.tablesdb('<DB>').table('<TABLE>').row(),
    response => console.log(response.payload),
    [Query.equal('status', ['active'])]  // server-side filtering
);

Use Presences API for online/typing/active state when supported; avoid durable DB rows + cleanup cron for ephemeral status.

Details: realtime.md


Functions

Init SDK outside handler. Group by domain. Event triggers, not polling. Functions: self-hosted uses Rule 2 Dart pin; Cloud uses latest SDK/runtime.

Details: functions.md | functions-advanced.md


Transactions

final tx = await tablesDB.createTransaction(ttl: 300);
await tablesDB.createRow(..., transactionId: tx.$id);
await tablesDB.updateTransaction(transactionId: tx.$id, commit: true);

Details: transactions.md


Relationships

await tablesDB.listRows(databaseId: 'db', tableId: 'posts',
    queries: [Query.equal('author.country', 'US'), Query.select(['title', 'author.name'])]);

Types: oneToOne | oneToMany | manyToOne | manyToMany

Details: relationships.md


Permissions

permissions: [
    Permission.read(Role.any()),
    Permission.update(Role.user(userId)),
    Permission.delete(Role.team('admin')),
    Permission.create(Role.label('premium')),
]

Default: deny all unless row/file perms set or inherited from table/bucket. Use row/file perms for per-resource ACL. If all resources share rules, set table/bucket perms, leave row/file perms empty. write = create + update + delete Avoid: missing perms = lockout; Role.any() + write/update/delete = public mutation; Permission.read(Role.any()) on sensitive data = public leak. Roles: any() | guests() | users() | user(id) | team(id) | team(id, role) | label(name) Details: permissions | teams | storage-files


Limits

Default page: 25 · Bulk: 1000 rows · Query.equal(): 100 values · Nesting: 3 levels · Queries/req: 100 · Timeout: 15s

Error Codes

400 Bad request · 401 Unauthorized · 403 Forbidden · 404 Not found · 409 Conflict · 429 Rate limited (client SDKs only) Catch AppwriteException. 429 -> exponential backoff.

Details: error-handling.md


Anti-Patterns

WrongRightWhy
N+1 queriesQuery.select(['col', 'relation.col'])Kills extra round-trips
Read-modify-writeOperator.increment()Race condition
Large offsetsQuery.cursorAfter(id)O(n) vs O(1)
Skip totalstotal: falseKills COUNT scan
Missing indexesCreate for queried columnsQueries scan entire table
SDK init inside handlerInit outside for warm reuseRepeated setup each call
Hardcoded secretsEnv varsSecurity risk
PollingRealtime or event triggersWasted executions
Client-side filteringRealtime queriesServer does work
Raw channel stringsChannel helpersTypos, no autocomplete
ColumnStringColumnVarchar or ColumnTextstring deprecated
Hand-writing typesappwrite generateSchema drift, no autocomplete
databases.listDocuments()tablesDB.listRows()Deprecated API
Raw Appwrite HTTP (fetch, requests, dio, package:http, curl)Official SDK packageVersion drift, auth mistakes, lost typed APIs
Custom/hardcoded unique IDsID.unique()Overflow risk, info leakage, collisions
Full re-fetch every syncQuery.updatedAfter() + per-table timestampsWastes bandwidth, slow
Loop w/ createRow()createRows() bulkN requests vs 1

Cost Optimization

  1. Query.select() — cuts bandwidth
  2. Cursor pagination + total: false — fastest queries
  3. Realtime over polling — one connection vs repeated calls
  4. Batch ops — 1 execution vs N
  5. WebP quality 80 — smallest files, universal support
  6. Init outside handler — fewer cold starts
  7. Budget cap — Organization → Billing → Budget cap

Details: cost-optimization.md


Reference Files

Data: schema-management · query-optimization · atomic-operators · relationships · transactions · bulk-operations · chunked-queries Performance: performance · pagination-performance · cost-optimization Auth: authentication · auth-methods · permissions · teams Services: storage-files · functions · functions-advanced · realtime · messaging · webhooks · avatars · graphql · locale Tooling: sdk-routing · appwrite-cli Platform: error-handling · limits · health · self-hosting · self-hosting-ops


Resources

Docs: https://appwrite.io/docs · API: https://appwrite.io/docs/references · SDKs: https://github.com/appwrite

Skills relacionados