← AppwriteCONTENT HISTORY

Update to Appwrite

Snapshot Sep 30, 2026 · 23:07 UTC · version 1.0.0

Collection source: not recorded for this historical snapshot.

WHAT CHANGED · RULE-BASED ANALYSIS

First saved snapshot

No earlier snapshot is available to establish a change.

Compare saved observations

Download comparison JSON
Full technical diff · 0 changed fields
Full snapshot data
{
  "name": "appwrite-php",
  "description": "Appwrite PHP SDK skill. Use when building server-side PHP applications with Appwrite, including Laravel and Symfony integrations. Covers user management, database/table CRUD, file storage, and functions via API keys.",
  "included_files": [],
  "skill_md_contents": "---\r\nname: appwrite-php\r\ndescription: Appwrite PHP SDK skill. Use when building server-side PHP applications with Appwrite, including Laravel and Symfony integrations. Covers user management, database/table CRUD, file storage, and functions via API keys.\r\n---\r\n\r\n\r\n# Appwrite PHP SDK\r\n\r\n## Installation\r\n\r\n```bash\r\ncomposer require appwrite/appwrite\r\n```\r\n\r\n## Setting Up the Client\r\n\r\n```php\r\nuse Appwrite\\Client;\r\nuse Appwrite\\ID;\r\nuse Appwrite\\Query;\r\nuse Appwrite\\Services\\Users;\r\nuse Appwrite\\Services\\TablesDB;\r\nuse Appwrite\\Services\\Storage;\r\nuse Appwrite\\Services\\Functions;\r\nuse Appwrite\\InputFile;\r\n\r\n$client = (new Client())\r\n    ->setEndpoint('https://<REGION>.cloud.appwrite.io/v1')\r\n    ->setProject(getenv('APPWRITE_PROJECT_ID'))\r\n    ->setKey(getenv('APPWRITE_API_KEY'));\r\n```\r\n\r\n## Code Examples\r\n\r\n### User Management\r\n\r\n```php\r\n$users = new Users($client);\r\n\r\n// Create user\r\n$user = $users->create(ID::unique(), 'user@example.com', null, 'password123', 'User Name');\r\n\r\n// List users\r\n$list = $users->list([Query::limit(25)]);\r\n\r\n// Get user\r\n$fetched = $users->get('[USER_ID]');\r\n\r\n// Delete user\r\n$users->delete('[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 arguments (PHP 8+, 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```php\r\n$tablesDB = new TablesDB($client);\r\n\r\n// Create database\r\n$db = $tablesDB->create(ID::unique(), 'My Database');\r\n\r\n// Create row\r\n$doc = $tablesDB->createRow('[DATABASE_ID]', '[TABLE_ID]', ID::unique(), [\r\n    'title' => 'Hello World'\r\n]);\r\n\r\n// Query rows\r\n$results = $tablesDB->listRows('[DATABASE_ID]', '[TABLE_ID]', [\r\n    Query::equal('title', ['Hello World']),\r\n    Query::limit(10)\r\n]);\r\n\r\n// Get row\r\n$row = $tablesDB->getRow('[DATABASE_ID]', '[TABLE_ID]', '[ROW_ID]');\r\n\r\n// Update row\r\n$tablesDB->updateRow('[DATABASE_ID]', '[TABLE_ID]', '[ROW_ID]', [\r\n    'title' => 'Updated'\r\n]);\r\n\r\n// Delete row\r\n$tablesDB->deleteRow('[DATABASE_ID]', '[TABLE_ID]', '[ROW_ID]');\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```php\r\n// Create table with explicit string column types\r\n$tablesDB->createTable('[DATABASE_ID]', ID::unique(), 'articles', [\r\n    ['key' => 'title',    'type' => 'varchar',    'size' => 255, 'required' => true],\r\n    ['key' => 'summary',  'type' => 'text',                      'required' => false],\r\n    ['key' => 'body',     'type' => 'mediumtext',                'required' => false],\r\n    ['key' => 'raw_data', 'type' => 'longtext',                  'required' => false],\r\n]);\r\n```\r\n\r\n### Query Methods\r\n\r\n```php\r\n// Filtering\r\nQuery::equal('field', ['value'])            // == (always pass array)\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 (string or array)\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```php\r\n$storage = new Storage($client);\r\n\r\n// Upload file\r\n$file = $storage->createFile('[BUCKET_ID]', ID::unique(), InputFile::withPath('/path/to/file.png'));\r\n\r\n// List files\r\n$files = $storage->listFiles('[BUCKET_ID]');\r\n\r\n// Delete file\r\n$storage->deleteFile('[BUCKET_ID]', '[FILE_ID]');\r\n```\r\n\r\n#### InputFile Factory Methods\r\n\r\n```php\r\nuse Appwrite\\InputFile;\r\n\r\nInputFile::withPath('/path/to/file.png')                    // from filesystem path\r\nInputFile::withData('Hello world', 'hello.txt')             // from string content\r\n```\r\n\r\n### Teams\r\n\r\n```php\r\n$teams = new Teams($client);\r\n\r\n// Create team\r\n$team = $teams->create(ID::unique(), 'Engineering');\r\n\r\n// List teams\r\n$list = $teams->list();\r\n\r\n// Create membership (invite user by email)\r\n$membership = $teams->createMembership('[TEAM_ID]', ['editor'], email: 'user@example.com');\r\n\r\n// List memberships\r\n$members = $teams->listMemberships('[TEAM_ID]');\r\n\r\n// Update membership roles\r\n$teams->updateMembership('[TEAM_ID]', '[MEMBERSHIP_ID]', ['admin']);\r\n\r\n// Delete team\r\n$teams->delete('[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### Serverless Functions\r\n\r\n```php\r\n$functions = new Functions($client);\r\n\r\n// Execute function\r\n$execution = $functions->createExecution('[FUNCTION_ID]', '{\"key\": \"value\"}');\r\n\r\n// List executions\r\n$executions = $functions->listExecutions('[FUNCTION_ID]');\r\n```\r\n\r\n#### Writing a Function Handler (PHP runtime)\r\n\r\n```php\r\n// src/main.php — Appwrite Function entry point\r\nreturn function ($context) {\r\n    // $context->req->body        — raw body (string)\r\n    // $context->req->bodyJson    — parsed JSON (array or null)\r\n    // $context->req->headers     — headers (array)\r\n    // $context->req->method      — HTTP method\r\n    // $context->req->path        — URL path\r\n    // $context->req->query       — query params (array)\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    $data = $context->req->bodyJson ?? [];\r\n    if (!isset($data['name'])) {\r\n        $context->error('Missing name field');\r\n        return $context->res->json(['error' => 'Name is required'], 400);\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 (Laravel, Symfony, etc.) use the **server SDK** 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```php\r\nuse Appwrite\\Client;\r\nuse Appwrite\\Services\\Account;\r\n\r\n// Admin client (reusable)\r\n$adminClient = (new Client())\r\n    ->setEndpoint('https://<REGION>.cloud.appwrite.io/v1')\r\n    ->setProject('[PROJECT_ID]')\r\n    ->setKey(getenv('APPWRITE_API_KEY'));\r\n\r\n// Session client (create per-request)\r\n$sessionClient = (new Client())\r\n    ->setEndpoint('https://<REGION>.cloud.appwrite.io/v1')\r\n    ->setProject('[PROJECT_ID]');\r\n\r\n$session = $_COOKIE['a_session_[PROJECT_ID]'] ?? null;\r\nif ($session) {\r\n    $sessionClient->setSession($session);\r\n}\r\n```\r\n\r\n#### Email/Password Login\r\n\r\n```php\r\n$account = new Account($adminClient);\r\n$session = $account->createEmailPasswordSession($email, $password);\r\n\r\n// Cookie name must be a_session_<PROJECT_ID>\r\nsetcookie('a_session_[PROJECT_ID]', $session['secret'], [\r\n    'httpOnly' => true,\r\n    'secure' => true,\r\n    'sameSite' => 'strict',\r\n    'expires' => strtotime($session['expire']),\r\n    'path' => '/',\r\n]);\r\n```\r\n\r\n#### Authenticated Requests\r\n\r\n```php\r\n$session = $_COOKIE['a_session_[PROJECT_ID]'] ?? null;\r\nif (!$session) {\r\n    http_response_code(401);\r\n    exit;\r\n}\r\n\r\n$sessionClient->setSession($session);\r\n$account = new Account($sessionClient);\r\n$user = $account->get();\r\n```\r\n\r\n#### OAuth2 SSR Flow\r\n\r\n```php\r\n// Step 1: Redirect to OAuth provider\r\n$account = new Account($adminClient);\r\n$redirectUrl = $account->createOAuth2Token(\r\n    OAuthProvider::GITHUB(),\r\n    'https://example.com/oauth/success',\r\n    'https://example.com/oauth/failure',\r\n);\r\nheader('Location: ' . $redirectUrl);\r\n\r\n// Step 2: Handle callback — exchange token for session\r\n$account = new Account($adminClient);\r\n$session = $account->createSession($_GET['userId'], $_GET['secret']);\r\n\r\nsetcookie('a_session_[PROJECT_ID]', $session['secret'], [\r\n    'httpOnly' => true, 'secure' => true, 'sameSite' => 'strict',\r\n    'expires' => strtotime($session['expire']), 'path' => '/',\r\n]);\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($_SERVER['HTTP_USER_AGENT'])` to record the end-user's browser info for debugging and security.\r\n\r\n## Error Handling\r\n\r\n```php\r\nuse Appwrite\\AppwriteException;\r\n\r\ntry {\r\n    $row = $tablesDB->getRow('[DATABASE_ID]', '[TABLE_ID]', '[ROW_ID]');\r\n} catch (AppwriteException $e) {\r\n    echo $e->getMessage();   // human-readable error message\r\n    echo $e->getCode();      // HTTP status code (int)\r\n    echo $e->getType();      // Appwrite error type string (e.g. 'document_not_found')\r\n    echo $e->getResponse();  // full response body (array)\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 for this action |\r\n| `404` | Not found — resource does not exist |\r\n| `409` | Conflict — duplicate ID or unique constraint violation |\r\n| `429` | Rate limited — too many requests, retry after backoff |\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```php\r\nuse Appwrite\\Permission;\r\nuse Appwrite\\Role;\r\n```\r\n\r\n### Database Row with Permissions\r\n\r\n```php\r\n$doc = $tablesDB->createRow('[DATABASE_ID]', '[TABLE_ID]', ID::unique(), [\r\n    'title' => 'Hello World'\r\n], [\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### File Upload with Permissions\r\n\r\n```php\r\n$file = $storage->createFile('[BUCKET_ID]', ID::unique(), InputFile::withPath('/path/to/file.png'), [\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> **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: 8629cd97919da121516ef5587a2ea723e4efbbfbb6dcf9d98b5585cf0adf4b4a