AgentStack
SKILL verified BSD-3-Clause Self-run

Appwrite Php

skill-appwrite-agent-skills-appwrite-php · by appwrite

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.

No reviews yet
0 installs
10 views
0.0% view→install

Install

$ agentstack add skill-appwrite-agent-skills-appwrite-php

✓ scanned · ✓ verified — works with Claude Code, Cursor, and more.

Security review

✓ Passed

No issues found. Passed automated security review. · v0.1.0 How review works →

  • Prompt-injection patterns
  • Secret / credential exfiltration
  • Dangerous shell & filesystem operations
  • Untrusted network calls
  • Known-malicious package signatures

What it can access

  • Network access No
  • Filesystem access No
  • Shell / process execution No
  • Environment & secrets Used
  • Dynamic code execution No

From automated source analysis of v0.1.0. “Used” means the capability is present in the source — more access means more to trust, not that it’s unsafe.

Are you the author of Appwrite Php? Claim this listing to set pricing, connect Stripe payouts, and keep 70% of every sale.
Sign up to claim

About

Appwrite PHP SDK

Installation

composer require appwrite/appwrite

Setting Up the Client

use Appwrite\Client;
use Appwrite\ID;
use Appwrite\Query;
use Appwrite\Services\Users;
use Appwrite\Services\TablesDB;
use Appwrite\Services\Storage;
use Appwrite\Services\Functions;
use Appwrite\InputFile;

$client = (new Client())
    ->setEndpoint('https://.cloud.appwrite.io/v1')
    ->setProject(getenv('APPWRITE_PROJECT_ID'))
    ->setKey(getenv('APPWRITE_API_KEY'));

Code Examples

User Management

$users = new Users($client);

// Create user
$user = $users->create(ID::unique(), 'user@example.com', null, 'password123', 'User Name');

// List users
$list = $users->list([Query::limit(25)]);

// Get user
$fetched = $users->get('[USER_ID]');

// Delete user
$users->delete('[USER_ID]');

Database Operations

> 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. > > 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.

$tablesDB = new TablesDB($client);

// Create database
$db = $tablesDB->create(ID::unique(), 'My Database');

// Create row
$doc = $tablesDB->createRow('[DATABASE_ID]', '[TABLE_ID]', ID::unique(), [
    'title' => 'Hello World'
]);

// Query rows
$results = $tablesDB->listRows('[DATABASE_ID]', '[TABLE_ID]', [
    Query::equal('title', ['Hello World']),
    Query::limit(10)
]);

// Get row
$row = $tablesDB->getRow('[DATABASE_ID]', '[TABLE_ID]', '[ROW_ID]');

// Update row
$tablesDB->updateRow('[DATABASE_ID]', '[TABLE_ID]', '[ROW_ID]', [
    'title' => 'Updated'
]);

// Delete row
$tablesDB->deleteRow('[DATABASE_ID]', '[TABLE_ID]', '[ROW_ID]');
String Column Types

> Note: The legacy string type is deprecated. Use explicit column types for all new columns.

| Type | Max characters | Indexing | Storage | |------|---------------|----------|---------| | varchar | 16,383 | Full index (if size ≤ 768) | Inline in row | | text | 16,383 | Prefix only | Off-page | | mediumtext | 4,194,303 | Prefix only | Off-page | | longtext | 1,073,741,823 | Prefix only | Off-page |

  • varchar is stored inline and counts towards the 64 KB row size limit. Prefer for short, indexed fields like names, slugs, or identifiers.
  • 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.
// Create table with explicit string column types
$tablesDB->createTable('[DATABASE_ID]', ID::unique(), 'articles', [
    ['key' => 'title',    'type' => 'varchar',    'size' => 255, 'required' => true],
    ['key' => 'summary',  'type' => 'text',                      'required' => false],
    ['key' => 'body',     'type' => 'mediumtext',                'required' => false],
    ['key' => 'raw_data', 'type' => 'longtext',                  'required' => false],
]);

Query Methods

// Filtering
Query::equal('field', ['value'])            // == (always pass array)
Query::notEqual('field', ['value'])         // !=
Query::lessThan('field', 100)              // 
Query::greaterThanEqual('field', 100)      // >=
Query::between('field', 1, 100)            // 1 createFile('[BUCKET_ID]', ID::unique(), InputFile::withPath('/path/to/file.png'));

// List files
$files = $storage->listFiles('[BUCKET_ID]');

// Delete file
$storage->deleteFile('[BUCKET_ID]', '[FILE_ID]');
InputFile Factory Methods
use Appwrite\InputFile;

InputFile::withPath('/path/to/file.png')                    // from filesystem path
InputFile::withData('Hello world', 'hello.txt')             // from string content

Teams

$teams = new Teams($client);

// Create team
$team = $teams->create(ID::unique(), 'Engineering');

// List teams
$list = $teams->list();

// Create membership (invite user by email)
$membership = $teams->createMembership('[TEAM_ID]', ['editor'], email: 'user@example.com');

// List memberships
$members = $teams->listMemberships('[TEAM_ID]');

// Update membership roles
$teams->updateMembership('[TEAM_ID]', '[MEMBERSHIP_ID]', ['admin']);

// Delete team
$teams->delete('[TEAM_ID]');

> 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.

Serverless Functions

$functions = new Functions($client);

// Execute function
$execution = $functions->createExecution('[FUNCTION_ID]', '{"key": "value"}');

// List executions
$executions = $functions->listExecutions('[FUNCTION_ID]');
Writing a Function Handler (PHP runtime)
// src/main.php — Appwrite Function entry point
return function ($context) {
    // $context->req->body        — raw body (string)
    // $context->req->bodyJson    — parsed JSON (array or null)
    // $context->req->headers     — headers (array)
    // $context->req->method      — HTTP method
    // $context->req->path        — URL path
    // $context->req->query       — query params (array)

    $context->log('Processing: ' . $context->req->method . ' ' . $context->req->path);

    if ($context->req->method === 'GET') {
        return $context->res->json(['message' => 'Hello from Appwrite Function!']);
    }

    $data = $context->req->bodyJson ?? [];
    if (!isset($data['name'])) {
        $context->error('Missing name field');
        return $context->res->json(['error' => 'Name is required'], 400);
    }

    return $context->res->json(['success' => true]);      // JSON
    // return $context->res->text('Hello');                // plain text
    // return $context->res->empty();                      // 204
    // return $context->res->redirect('https://...');      // 302
};

Server-Side Rendering (SSR) Authentication

SSR apps (Laravel, Symfony, etc.) use the server SDK to handle auth. You need two clients:

  • Admin client — uses an API key, creates sessions, bypasses rate limits (reusable singleton)
  • Session client — uses a session cookie, acts on behalf of a user (create per-request, never share)
use Appwrite\Client;
use Appwrite\Services\Account;

// Admin client (reusable)
$adminClient = (new Client())
    ->setEndpoint('https://.cloud.appwrite.io/v1')
    ->setProject('[PROJECT_ID]')
    ->setKey(getenv('APPWRITE_API_KEY'));

// Session client (create per-request)
$sessionClient = (new Client())
    ->setEndpoint('https://.cloud.appwrite.io/v1')
    ->setProject('[PROJECT_ID]');

$session = $_COOKIE['a_session_[PROJECT_ID]'] ?? null;
if ($session) {
    $sessionClient->setSession($session);
}
Email/Password Login
$account = new Account($adminClient);
$session = $account->createEmailPasswordSession($email, $password);

// Cookie name must be a_session_
setcookie('a_session_[PROJECT_ID]', $session['secret'], [
    'httpOnly' => true,
    'secure' => true,
    'sameSite' => 'strict',
    'expires' => strtotime($session['expire']),
    'path' => '/',
]);
Authenticated Requests
$session = $_COOKIE['a_session_[PROJECT_ID]'] ?? null;
if (!$session) {
    http_response_code(401);
    exit;
}

$sessionClient->setSession($session);
$account = new Account($sessionClient);
$user = $account->get();
OAuth2 SSR Flow
// Step 1: Redirect to OAuth provider
$account = new Account($adminClient);
$redirectUrl = $account->createOAuth2Token(
    OAuthProvider::GITHUB(),
    'https://example.com/oauth/success',
    'https://example.com/oauth/failure',
);
header('Location: ' . $redirectUrl);

// Step 2: Handle callback — exchange token for session
$account = new Account($adminClient);
$session = $account->createSession($_GET['userId'], $_GET['secret']);

setcookie('a_session_[PROJECT_ID]', $session['secret'], [
    'httpOnly' => true, 'secure' => true, 'sameSite' => 'strict',
    'expires' => strtotime($session['expire']), 'path' => '/',
]);

> Cookie security: Always use httpOnly, secure, and sameSite: 'strict' to prevent XSS. The cookie name must be a_session_.

> Forwarding user agent: Call $sessionClient->setForwardedUserAgent($_SERVER['HTTP_USER_AGENT']) to record the end-user's browser info for debugging and security.

Error Handling

use Appwrite\AppwriteException;

try {
    $row = $tablesDB->getRow('[DATABASE_ID]', '[TABLE_ID]', '[ROW_ID]');
} catch (AppwriteException $e) {
    echo $e->getMessage();   // human-readable error message
    echo $e->getCode();      // HTTP status code (int)
    echo $e->getType();      // Appwrite error type string (e.g. 'document_not_found')
    echo $e->getResponse();  // full response body (array)
}

Common error codes:

| Code | Meaning | |------|---------| | 401 | Unauthorized — missing or invalid session/API key | | 403 | Forbidden — insufficient permissions for this action | | 404 | Not found — resource does not exist | | 409 | Conflict — duplicate ID or unique constraint violation | | 429 | Rate limited — too many requests, retry after backoff |

Permissions & Roles (Critical)

Appwrite 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.

use Appwrite\Permission;
use Appwrite\Role;

Database Row with Permissions

$doc = $tablesDB->createRow('[DATABASE_ID]', '[TABLE_ID]', ID::unique(), [
    'title' => 'Hello World'
], [
    Permission::read(Role::user('[USER_ID]')),     // specific user can read
    Permission::update(Role::user('[USER_ID]')),   // specific user can update
    Permission::read(Role::team('[TEAM_ID]')),     // all team members can read
    Permission::read(Role::any()),                 // anyone (including guests) can read
]);

File Upload with Permissions

$file = $storage->createFile('[BUCKET_ID]', ID::unique(), InputFile::withPath('/path/to/file.png'), [
    Permission::read(Role::any()),
    Permission::update(Role::user('[USER_ID]')),
    Permission::delete(Role::user('[USER_ID]')),
]);

> 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.

> Common mistakes: > - Forgetting permissions — the resource becomes inaccessible to all users (including the creator) > - Role::any() with write/update/delete — allows any user, including unauthenticated guests, to modify or remove the resource > - Permission::read(Role::any()) on sensitive data — makes the resource publicly readable

Source & license

This open-source skill is cataloged on AgentStack and links to its original source — we do not rehost the code.

Install and usage instructions live in the source repository linked above.

Reviews

No reviews yet — be the first.

Versions

  • v0.1.0 Imported from the upstream source.