Appwrite Development
Route
Load the owner before acting. Unlisted detail = read the owner, never infer.
| Trigger |
Owner |
| Any Appwrite CLI/wrapper command, deployment, schema sync, function/site variable operation, or CLI failure — before installing, binding, probing, diagnosing, or mutating |
appwrite-cli |
| Production schema/data/ACL/function cutover |
production-migrations + CLI owner when CLI participates |
| TablesDB transaction or cross-service consistency |
transactions + permissions |
| Permanent account/subject-data erasure across TablesDB, Auth, Storage, provider data, or retained audit evidence |
destructive-erasure + transactions |
| Mass row create/update/upsert/delete, transaction-limit pressure, per-row write loop |
bulk-operations + transactions when atomic scope spans requests/tables |
Filter by more IDs than the deployed Query.equal() value cap |
chunked-queries |
| Table/column/index design, encryption at rest, auto-increment, timestamp override, CSV import/export |
schema-management |
Query shape, select, spatial, time helpers, missing-index suspicion |
query-optimization |
| Counter, array, string, or date field mutated concurrently |
atomic-operators |
| Relationship modeling or traversal |
relationships |
| List, feed, infinite scroll, or large-offset paging |
pagination-performance |
| Slow path, caching, delta sync, bootstrap ordering |
performance |
| Bandwidth, execution, or storage cost |
cost-optimization |
| Sessions, MFA, SSR auth, JWT, user labels, security settings |
authentication |
| OAuth, magic link, email OTP, phone, anonymous, custom token |
auth-methods |
| ACL design, lockout, public-leak suspicion |
permissions |
| Team, membership, or multi-tenancy |
teams |
| Upload, download, preview, transform, bucket config |
storage-files |
| Function authoring, handler, runtime, cold start, env vars |
functions |
| Function events, schedules, idempotency, binary payloads, CI/CD |
functions-advanced |
| Function execution SDK model or response-format parsing failure after remote work starts |
functions-advanced |
| Realtime subscription, channel, presence, event filtering |
realtime |
| Push, email, or SMS delivery |
messaging |
| Outbound event delivery to an external system |
webhooks |
| Avatar, initials, QR, flag, favicon |
avatars |
429, retry, typed error, timeout, code-zero transport failure, client request burst, partial-sync report |
error-handling + performance |
| Platform ceiling or limit error |
limits |
| Country, currency, language, or geo lookup |
locale |
| GraphQL endpoint |
graphql |
| Appwrite MCP server setup for a coding agent, or Appwrite documentation lookup |
mcp-servers |
| Self-hosted install, config, security, scaling, SDK version pins |
self-hosting |
| Self-hosted backup, restore, upgrade, data-loss incident |
self-hosting-ops |
| Health check, queue depth, uptime monitoring |
health |
Invariants
- Official SDK only — raw Appwrite HTTP (
fetch, requests, dio, package:http, curl) is a violation unless the SDK lacks the endpoint or an isolated, tested Client.call works around SDK model parsing.
- Pin SDKs by target and call shape — Cloud: latest stable official SDK. Self-hosted
1.9.x: exact release-matched pins in self-hosting. “Compatible with 1.9.x” does not mean release-matched. Before changing a pin, audit every intervening breaking change and prove the repository's real SDK calls against the candidate; version resolution alone is insufficient. Repository-pinned binary/wrapper version always outranks a skill pin.
- TablesDB, not Collections — Collections/Documents API deprecated 1.8.0.
- Allocate Appwrite IDs once with
ID.unique() — retryable create: call ID.unique() before the first attempt → persist the returned ID in the durable draft/intent → reuse that exact ID for every retry/reconciliation. A fresh ID.unique() on retry creates a second resource. Business/natural identity remains in indexed columns; never derive resource IDs from names, timestamps, slugs, hashes, or custom generators.
- Explicit ACL — server SDK/Console create = empty resource ACL; client SDK create = creator read/update/delete. Pass explicit
Permission/Role whenever ACL correctness matters.
- Bind limits to the deployed target — page size, bulk rows/request, transaction operations, and
Query.equal() value cap come from the deployed server source/config, never from memory.
- Async-start long-running Functions — client
createExecution for delete/sync/import/export/migrate/generate uses async execution, then reconciles source-of-truth state with bounded polling/realtime/fetch. Report destructive failure only after reconciliation proves the entity still exists. A synchronous createExecution already returns the terminal execution → read responseStatusCode + responseBody off that response; polling getExecution from the creating session returns 404 and inverts a success into a failure. Unavoidable status poll → 404 = terminal-unknown, never an error branch. Use functions-advanced.
- Guard schema pushes —
appwrite push tables reconciles remote TablesDB against the complete local manifest; omission means deletion. Production push requires appwrite-cli inventory + manifest guard PASS. push all, --all, and --force never substitute for that gate.
- Stage production migrations — additive expand → type-aware resumable backfill → compatible deployment → contract/read-back → consumer activation. Partial data/schema never activates downstream code. Use production-migrations.
- Preserve write intent before optimizing — update-only work never routes through
upsertRow/upsertRows; a pre-read, existence check, or full payload does not remove create-on-missing semantics. Same patch across rows → updateRows; heterogeneous per-row updates → createOperations with action: update inside the verified transaction budget, or redesign. Transaction pressure never authorizes upsert. Use bulk-operations.
- Batch collection writes before coding — target count can exceed one or is data-dependent → inventory the full mutation set + deployed limits before implementation. Compatible server bulk method exists → per-row write loop is forbidden. Bulk is unsupported → complete operation budget + atomic late-failure proof required. Full plan over cap → redesign or resumable fixed-point workflow; never split one atomic invariant across committed batches. Use bulk-operations + transactions.
- Preserve failure causality — cleanup, compensation, or rollback failure never replaces the primary exception. Retain both errors + stack traces + execution/transaction IDs, report the operation failed, then reconcile the exact postcondition. Use transactions.
- Coordinate client demand and uncertain outcomes — one endpoint/project-scoped coordinator owns foreground, sync, auth, and retry traffic. Bound concurrency; share 429/transport cooldowns; classify code-zero network failures; retry reads within one deadline; reconcile writes/transactions before any repeat; report one incident per failed operation; and never advance a sync checkpoint after partial failure. Use error-handling.
SDK Routing
| Runtime |
Package |
| Web TypeScript/JavaScript/React |
appwrite |
| Node.js/Deno/TypeScript SSR/Functions |
node-appwrite |
| Flutter client |
appwrite |
| Dart server/Functions |
dart_appwrite |
| Python server/Functions |
appwrite |
- Call style: TypeScript object parameters, Python keyword arguments, Dart named parameters. Positional only when matching existing code or on explicit request.
- Client SDKs use account sessions and user-scoped APIs. Server SDKs use API keys. SSR uses two clients: a reusable admin client for session creation, and a per-request session client via
setSession(...) — never shared.
- Cloud project endpoint =
https://<REGION>.cloud.appwrite.io/v1. The CLI account login endpoint stays https://cloud.appwrite.io/v1; do not rewrite it to a region.
- Initialize clients outside warm Function handlers where the runtime allows.
import { Client, TablesDB } from 'node-appwrite';
const client = new Client()
.setEndpoint('https://<REGION>.cloud.appwrite.io/v1')
.setProject('<PROJECT_ID>')
.setKey('<API_KEY>');
const tablesDB = new TablesDB(client);
from appwrite.client import Client
from appwrite.services.tables_db import TablesDB
client = (Client()
.set_endpoint('https://<REGION>.cloud.appwrite.io/v1')
.set_project('<PROJECT_ID>')
.set_key('<API_KEY>'))
tables_db = TablesDB(client)
import 'package:dart_appwrite/dart_appwrite.dart';
final client = Client()
.setEndpoint('https://<REGION>.cloud.appwrite.io/v1')
.setProject('<PROJECT_ID>')
.setKey('<API_KEY>');
final tablesDB = TablesDB(client);
Terminology (1.8.0+)
Collections = Tables · Documents = Rows · Attributes = Columns · Databases = TablesDB
Core Shapes
Row ops: createRow · getRow · listRows · updateRow · upsertRow · deleteRow
Bulk (server SDK only, atomic per request, rejects relationship columns): createRows · updateRows · upsertRows · deleteRows
await tablesDB.createRow(databaseId: 'db', tableId: 'users', rowId: ID.unique(),
data: {'name': 'Alice'});
final rows = await tablesDB.listRows(databaseId: 'db', tableId: 'users',
queries: [Query.equal('status', 'active'), Query.select(['name', 'email'])]);
Query (all prefixed Query.; per-SDK naming + semantics in query-optimization):
equal · notEqual · lessThan · lessThanEqual · greaterThan · greaterThanEqual · between · notBetween · startsWith · endsWith · contains · search (+ not variants) · isNull · isNotNull · and · or · select · limit · offset · cursorAfter · cursorBefore · orderAsc · orderDesc · orderRandom · createdAfter · createdBefore · updatedAfter · updatedBefore · distanceEqual · distanceLessThan · distanceGreaterThan · intersects · overlaps · touches · crosses
Operator (atomic field mutation; semantics in atomic-operators):
increment · decrement · multiply · divide · arrayAppend · arrayPrepend · arrayRemove · arrayUnique · arrayIntersect · arrayDiff · toggle · stringConcat · stringReplace · dateAddDays · dateSetNow
Column types (string deprecated; full table + storage/index tradeoffs in schema-management):
varchar · text · mediumtext · longtext · integer · bigint · float · boolean · datetime · email · url · ip · enum · relationship · point · line · polygon
Realtime channels (type-safe Channel helpers preferred over raw strings):
account · tablesdb.<DB>.tables.<TABLE>.rows[.<ROW>] · buckets.<BUCKET>.files[.<FILE>] · teams[.<TEAM>] · memberships[.<MEMBERSHIP>] · functions.<FUNCTION>.executions · presences[.<PRESENCE>]
Anti-Patterns
| Wrong |
Right |
Why |
Raw Appwrite HTTP (fetch, requests, dio, package:http, curl) |
Official SDK package |
Version drift, auth mistakes, lost typed APIs |
databases.listDocuments() |
tablesDB.listRows() |
Deprecated API |
ColumnString |
ColumnVarchar or ColumnText |
string deprecated |
Derived/custom resource ID, or fresh ID.unique() per retry |
Preallocate one ID.unique(), persist, reuse |
Leakage/collision, or duplicate resource |
| N+1 relationship fetches |
Query.select(['col', 'relation.col']) |
Kills extra round-trips |
| Read-modify-write |
Operator.increment() |
Race condition |
| Large offsets |
Query.cursorAfter(id) |
O(n) vs O(1) |
| Fetching totals by default |
total: false |
Kills COUNT scan |
total as an in-transaction completeness/uniqueness guard |
total: false + Query.limit(n + 1) + assert rows.length — transactions |
Staged rows drop out of total but stay in rows |
| Missing indexes |
Index every queried/ordered column |
Full table scan |
| Full re-fetch every sync |
Query.updatedAfter() + per-table timestamps |
Wastes bandwidth |
| Loop with per-row create/update/delete |
Matching bulk call |
N requests + N transaction ops vs 1 |
| Treating bulk as partial-success |
One bulk request is atomic; reconcile exact postcondition |
Appwrite bulk is all-or-nothing per request |
| Empty bulk update/delete queries |
Reject unless an explicit all-rows operation is authorized |
Empty queries target every row |
| SDK init inside handler |
Init outside for warm reuse |
Repeated setup per call |
| Polling |
Realtime or event triggers |
Wasted executions |
| Client-side event filtering |
Realtime queries |
Server does the work |
| Raw channel strings |
Channel helpers |
Typos, no autocomplete |
| Hand-written types |
appwrite generate |
Schema drift, no autocomplete |
| Durable rows + cleanup cron for online/typing state |
Presences API |
Ephemeral state does not belong in a table |
| Hardcoded secrets |
Env vars / secret manager |
Security risk |
| One function per operation |
One function per domain |
Cold starts, deploy sprawl |
| Independent retry loop per datasource/sync table |
One shared request coordinator + cooldown |
Concurrent retries recreate the outage and 429 burst |
| Retry a timed-out write immediately |
Read exact postcondition, then reuse the persisted ID or rebuild |
The first write may already have succeeded |
| Report cause + partial-sync wrapper separately |
One operation incident with failed-table context |
Duplicate groups hide the root cause |
Resources
Docs https://appwrite.io/docs · API https://appwrite.io/docs/references · SDKs https://github.com/appwrite
1---2name: appwrite-backend3description: Appwrite backend development and operations, including destructive account/data erasure and MCP server wiring for coding agents. Use for Appwrite SDK work; any Appwrite CLI command or failure must route through the CLI safety branch.4license: MIT5---67# Appwrite Development89## Route1011Load the owner before acting. Unlisted detail = read the owner, never infer.1213| Trigger | Owner |14|---|---|15| Any Appwrite CLI/wrapper command, deployment, schema sync, function/site variable operation, or CLI failure — before installing, binding, probing, diagnosing, or mutating | [appwrite-cli](references/appwrite-cli.md) |16| Production schema/data/ACL/function cutover | [production-migrations](references/production-migrations.md) + CLI owner when CLI participates |17| TablesDB transaction or cross-service consistency | [transactions](references/transactions.md) + [permissions](references/permissions.md) |18| Permanent account/subject-data erasure across TablesDB, Auth, Storage, provider data, or retained audit evidence | [destructive-erasure](references/destructive-erasure.md) + [transactions](references/transactions.md) |19| Mass row create/update/upsert/delete, transaction-limit pressure, per-row write loop | [bulk-operations](references/bulk-operations.md) + [transactions](references/transactions.md) when atomic scope spans requests/tables |20| Filter by more IDs than the deployed `Query.equal()` value cap | [chunked-queries](references/chunked-queries.md) |21| Table/column/index design, encryption at rest, auto-increment, timestamp override, CSV import/export | [schema-management](references/schema-management.md) |22| Query shape, `select`, spatial, time helpers, missing-index suspicion | [query-optimization](references/query-optimization.md) |23| Counter, array, string, or date field mutated concurrently | [atomic-operators](references/atomic-operators.md) |24| Relationship modeling or traversal | [relationships](references/relationships.md) |25| List, feed, infinite scroll, or large-offset paging | [pagination-performance](references/pagination-performance.md) |26| Slow path, caching, delta sync, bootstrap ordering | [performance](references/performance.md) |27| Bandwidth, execution, or storage cost | [cost-optimization](references/cost-optimization.md) |28| Sessions, MFA, SSR auth, JWT, user labels, security settings | [authentication](references/authentication.md) |29| OAuth, magic link, email OTP, phone, anonymous, custom token | [auth-methods](references/auth-methods.md) |30| ACL design, lockout, public-leak suspicion | [permissions](references/permissions.md) |31| Team, membership, or multi-tenancy | [teams](references/teams.md) |32| Upload, download, preview, transform, bucket config | [storage-files](references/storage-files.md) |33| Function authoring, handler, runtime, cold start, env vars | [functions](references/functions.md) |34| Function events, schedules, idempotency, binary payloads, CI/CD | [functions-advanced](references/functions-advanced.md) |35| Function execution SDK model or response-format parsing failure after remote work starts | [functions-advanced](references/functions-advanced.md) |36| Realtime subscription, channel, presence, event filtering | [realtime](references/realtime.md) |37| Push, email, or SMS delivery | [messaging](references/messaging.md) |38| Outbound event delivery to an external system | [webhooks](references/webhooks.md) |39| Avatar, initials, QR, flag, favicon | [avatars](references/avatars.md) |40| `429`, retry, typed error, timeout, code-zero transport failure, client request burst, partial-sync report | [error-handling](references/error-handling.md) + [performance](references/performance.md) |41| Platform ceiling or limit error | [limits](references/limits.md) |42| Country, currency, language, or geo lookup | [locale](references/locale.md) |43| GraphQL endpoint | [graphql](references/graphql.md) |44| Appwrite MCP server setup for a coding agent, or Appwrite documentation lookup | [mcp-servers](references/mcp-servers.md) |45| Self-hosted install, config, security, scaling, SDK version pins | [self-hosting](references/self-hosting.md) |46| Self-hosted backup, restore, upgrade, data-loss incident | [self-hosting-ops](references/self-hosting-ops.md) |47| Health check, queue depth, uptime monitoring | [health](references/health.md) |4849## Invariants50511. **Official SDK only** — raw Appwrite HTTP (`fetch`, `requests`, `dio`, `package:http`, `curl`) is a violation unless the SDK lacks the endpoint or an isolated, tested `Client.call` works around SDK model parsing.522. **Pin SDKs by target and call shape** — Cloud: latest stable official SDK. Self-hosted `1.9.x`: exact release-matched pins in [self-hosting](references/self-hosting.md). “Compatible with `1.9.x`” does not mean release-matched. Before changing a pin, audit every intervening breaking change and prove the repository's real SDK calls against the candidate; version resolution alone is insufficient. Repository-pinned binary/wrapper version always outranks a skill pin.533. **TablesDB, not Collections** — Collections/Documents API deprecated 1.8.0.544. **Allocate Appwrite IDs once with `ID.unique()`** — retryable create: call `ID.unique()` before the first attempt → persist the returned ID in the durable draft/intent → reuse that exact ID for every retry/reconciliation. A fresh `ID.unique()` on retry creates a second resource. Business/natural identity remains in indexed columns; never derive resource IDs from names, timestamps, slugs, hashes, or custom generators.555. **Explicit ACL** — server SDK/Console create = empty resource ACL; client SDK create = creator read/update/delete. Pass explicit `Permission`/`Role` whenever ACL correctness matters.566. **Bind limits to the deployed target** — page size, bulk rows/request, transaction operations, and `Query.equal()` value cap come from the deployed server source/config, never from memory.577. **Async-start long-running Functions** — client `createExecution` for delete/sync/import/export/migrate/generate uses async execution, then reconciles source-of-truth state with bounded polling/realtime/fetch. Report destructive failure only after reconciliation proves the entity still exists. A synchronous `createExecution` already returns the terminal execution → read `responseStatusCode` + `responseBody` off that response; polling `getExecution` from the creating session returns `404` and inverts a success into a failure. Unavoidable status poll → `404` = terminal-unknown, never an error branch. Use [functions-advanced](references/functions-advanced.md).588. **Guard schema pushes** — `appwrite push tables` reconciles remote TablesDB against the complete local manifest; omission means deletion. Production push requires [appwrite-cli](references/appwrite-cli.md) inventory + manifest guard PASS. `push all`, `--all`, and `--force` never substitute for that gate.599. **Stage production migrations** — additive expand → type-aware resumable backfill → compatible deployment → contract/read-back → consumer activation. Partial data/schema never activates downstream code. Use [production-migrations](references/production-migrations.md).6010. **Preserve write intent before optimizing** — update-only work never routes through `upsertRow`/`upsertRows`; a pre-read, existence check, or full payload does not remove create-on-missing semantics. Same patch across rows → `updateRows`; heterogeneous per-row updates → `createOperations` with `action: update` inside the verified transaction budget, or redesign. Transaction pressure never authorizes upsert. Use [bulk-operations](references/bulk-operations.md).6111. **Batch collection writes before coding** — target count can exceed one or is data-dependent → inventory the full mutation set + deployed limits before implementation. Compatible server bulk method exists → per-row write loop is forbidden. Bulk is unsupported → complete operation budget + atomic late-failure proof required. Full plan over cap → redesign or resumable fixed-point workflow; never split one atomic invariant across committed batches. Use [bulk-operations](references/bulk-operations.md) + [transactions](references/transactions.md).6212. **Preserve failure causality** — cleanup, compensation, or rollback failure never replaces the primary exception. Retain both errors + stack traces + execution/transaction IDs, report the operation failed, then reconcile the exact postcondition. Use [transactions](references/transactions.md).6313. **Coordinate client demand and uncertain outcomes** — one endpoint/project-scoped coordinator owns foreground, sync, auth, and retry traffic. Bound concurrency; share 429/transport cooldowns; classify code-zero network failures; retry reads within one deadline; reconcile writes/transactions before any repeat; report one incident per failed operation; and never advance a sync checkpoint after partial failure. Use [error-handling](references/error-handling.md).6465## SDK Routing6667| Runtime | Package |68|---|---|69| Web TypeScript/JavaScript/React | `appwrite` |70| Node.js/Deno/TypeScript SSR/Functions | `node-appwrite` |71| Flutter client | `appwrite` |72| Dart server/Functions | `dart_appwrite` |73| Python server/Functions | `appwrite` |7475- Call style: TypeScript object parameters, Python keyword arguments, Dart named parameters. Positional only when matching existing code or on explicit request.76- Client SDKs use account sessions and user-scoped APIs. Server SDKs use API keys. SSR uses two clients: a reusable admin client for session creation, and a per-request session client via `setSession(...)` — never shared.77- Cloud project endpoint = `https://<REGION>.cloud.appwrite.io/v1`. The CLI account login endpoint stays `https://cloud.appwrite.io/v1`; do not rewrite it to a region.78- Initialize clients outside warm Function handlers where the runtime allows.7980```typescript81import { Client, TablesDB } from 'node-appwrite';82const client = new Client()83 .setEndpoint('https://<REGION>.cloud.appwrite.io/v1')84 .setProject('<PROJECT_ID>')85 .setKey('<API_KEY>');86const tablesDB = new TablesDB(client);87```8889```python90from appwrite.client import Client91from appwrite.services.tables_db import TablesDB92client = (Client()93 .set_endpoint('https://<REGION>.cloud.appwrite.io/v1')94 .set_project('<PROJECT_ID>')95 .set_key('<API_KEY>'))96tables_db = TablesDB(client)97```9899```dart100import 'package:dart_appwrite/dart_appwrite.dart';101final client = Client()102 .setEndpoint('https://<REGION>.cloud.appwrite.io/v1')103 .setProject('<PROJECT_ID>')104 .setKey('<API_KEY>');105final tablesDB = TablesDB(client);106```107108## Terminology (1.8.0+)109110Collections = Tables · Documents = Rows · Attributes = Columns · Databases = TablesDB111112## Core Shapes113114Row ops: `createRow` · `getRow` · `listRows` · `updateRow` · `upsertRow` · `deleteRow`115Bulk (server SDK only, atomic per request, rejects relationship columns): `createRows` · `updateRows` · `upsertRows` · `deleteRows`116117```dart118await tablesDB.createRow(databaseId: 'db', tableId: 'users', rowId: ID.unique(),119 data: {'name': 'Alice'});120121final rows = await tablesDB.listRows(databaseId: 'db', tableId: 'users',122 queries: [Query.equal('status', 'active'), Query.select(['name', 'email'])]);123```124125**Query** (all prefixed `Query.`; per-SDK naming + semantics in [query-optimization](references/query-optimization.md)):126`equal` · `notEqual` · `lessThan` · `lessThanEqual` · `greaterThan` · `greaterThanEqual` · `between` · `notBetween` · `startsWith` · `endsWith` · `contains` · `search` (+ `not` variants) · `isNull` · `isNotNull` · `and` · `or` · `select` · `limit` · `offset` · `cursorAfter` · `cursorBefore` · `orderAsc` · `orderDesc` · `orderRandom` · `createdAfter` · `createdBefore` · `updatedAfter` · `updatedBefore` · `distanceEqual` · `distanceLessThan` · `distanceGreaterThan` · `intersects` · `overlaps` · `touches` · `crosses`127128**Operator** (atomic field mutation; semantics in [atomic-operators](references/atomic-operators.md)):129`increment` · `decrement` · `multiply` · `divide` · `arrayAppend` · `arrayPrepend` · `arrayRemove` · `arrayUnique` · `arrayIntersect` · `arrayDiff` · `toggle` · `stringConcat` · `stringReplace` · `dateAddDays` · `dateSetNow`130131**Column types** (`string` deprecated; full table + storage/index tradeoffs in [schema-management](references/schema-management.md)):132`varchar` · `text` · `mediumtext` · `longtext` · `integer` · `bigint` · `float` · `boolean` · `datetime` · `email` · `url` · `ip` · `enum` · `relationship` · `point` · `line` · `polygon`133134**Realtime channels** (type-safe `Channel` helpers preferred over raw strings):135`account` · `tablesdb.<DB>.tables.<TABLE>.rows[.<ROW>]` · `buckets.<BUCKET>.files[.<FILE>]` · `teams[.<TEAM>]` · `memberships[.<MEMBERSHIP>]` · `functions.<FUNCTION>.executions` · `presences[.<PRESENCE>]`136137## Anti-Patterns138139| Wrong | Right | Why |140|---|---|---|141| Raw Appwrite HTTP (`fetch`, `requests`, `dio`, `package:http`, `curl`) | Official SDK package | Version drift, auth mistakes, lost typed APIs |142| `databases.listDocuments()` | `tablesDB.listRows()` | Deprecated API |143| `ColumnString` | `ColumnVarchar` or `ColumnText` | `string` deprecated |144| Derived/custom resource ID, or fresh `ID.unique()` per retry | Preallocate one `ID.unique()`, persist, reuse | Leakage/collision, or duplicate resource |145| N+1 relationship fetches | `Query.select(['col', 'relation.col'])` | Kills extra round-trips |146| Read-modify-write | `Operator.increment()` | Race condition |147| Large offsets | `Query.cursorAfter(id)` | O(n) vs O(1) |148| Fetching totals by default | `total: false` | Kills COUNT scan |149| `total` as an in-transaction completeness/uniqueness guard | `total: false` + `Query.limit(n + 1)` + assert `rows.length` — [transactions](references/transactions.md) | Staged rows drop out of `total` but stay in `rows` |150| Missing indexes | Index every queried/ordered column | Full table scan |151| Full re-fetch every sync | `Query.updatedAfter()` + per-table timestamps | Wastes bandwidth |152| Loop with per-row create/update/delete | Matching bulk call | N requests + N transaction ops vs 1 |153| Treating bulk as partial-success | One bulk request is atomic; reconcile exact postcondition | Appwrite bulk is all-or-nothing per request |154| Empty bulk update/delete queries | Reject unless an explicit all-rows operation is authorized | Empty queries target every row |155| SDK init inside handler | Init outside for warm reuse | Repeated setup per call |156| Polling | Realtime or event triggers | Wasted executions |157| Client-side event filtering | Realtime queries | Server does the work |158| Raw channel strings | `Channel` helpers | Typos, no autocomplete |159| Hand-written types | `appwrite generate` | Schema drift, no autocomplete |160| Durable rows + cleanup cron for online/typing state | Presences API | Ephemeral state does not belong in a table |161| Hardcoded secrets | Env vars / secret manager | Security risk |162| One function per operation | One function per domain | Cold starts, deploy sprawl |163| Independent retry loop per datasource/sync table | One shared request coordinator + cooldown | Concurrent retries recreate the outage and 429 burst |164| Retry a timed-out write immediately | Read exact postcondition, then reuse the persisted ID or rebuild | The first write may already have succeeded |165| Report cause + partial-sync wrapper separately | One operation incident with failed-table context | Duplicate groups hide the root cause |166167## Resources168169Docs <https://appwrite.io/docs> · API <https://appwrite.io/docs/references> · SDKs <https://github.com/appwrite>