appwrite/AGENTS.md
2026-04-09 15:18:26 +02:00

5.6 KiB

Appwrite

Self-hosted Backend-as-a-Service platform. Hybrid monolithic-microservice architecture built with PHP 8.3+ on Swoole, delivered as Docker containers.

Commands

Command Purpose
docker compose up -d --force-recreate --build Build and start all services
docker compose exec appwrite test tests/e2e/Services/[Service] Run E2E tests for a service
docker compose exec appwrite test tests/e2e/Services/[Service] --filter=[Method] Run a single test method
docker compose exec appwrite test tests/unit/ Run unit tests
composer format Auto-format code (Pint, PSR-12)
composer format <file> Format a specific file
composer lint <file> Check formatting of a file
composer analyze Static analysis (PHPStan level 3)
composer check Same as analyze

Stack

  • PHP 8.3+, Swoole 6.x (async runtime, replaces PHP-FPM)
  • Utopia PHP framework (HTTP routing, CLI, DI, queue)
  • MongoDB (default), MariaDB, MySQL, PostgreSQL (adapters via utopia-php/database)
  • Redis (cache, queue, pub/sub)
  • Docker + Traefik (reverse proxy)
  • PHPUnit 12, Pint (PSR-12), PHPStan level 3

Project layout

  • src/Appwrite/Platform/Modules/ -- feature modules (Account, Avatars, Compute, Console, Databases, Functions, Health, Project, Projects, Proxy, Sites, Storage, Teams, Tokens, VCS, Webhooks)
  • src/Appwrite/Platform/Workers/ -- background job workers
  • src/Appwrite/Platform/Tasks/ -- CLI tasks
  • app/init.php -- bootstrap (registers services, resources, listeners)
  • app/init/ -- configs, constants, locales, models, registers, resources, span, database filters/formats
  • bin/ -- CLI entry points: worker-* (14 workers), schedule-*, queue-*, plus doctor, install, migrate, realtime, upgrade, ssl, vars, maintenance, interval, specs, sdks, etc.
  • tests/e2e/ -- end-to-end tests per service
  • tests/unit/ -- unit tests
  • public/ -- static assets and generated SDKs

Module structure

Each module under src/Appwrite/Platform/Modules/{Name}/ contains:

Module.php           -- registers all services for the module
Services/Http.php    -- registers HTTP endpoints
Services/Workers.php -- registers background workers
Services/Tasks.php   -- registers CLI tasks
Http/{Service}/      -- endpoint actions (Create.php, Get.php, Update.php, Delete.php, XList.php)
Workers/             -- worker implementations
Tasks/               -- CLI task implementations

HTTP endpoint nesting reflects the URL path. Sub-resources get subdirectories. For example, within the Functions module: Http/Deployments/Template/Create.php -> POST /v1/functions/:functionId/deployments/template

File names in Http directories must only be Get.php, Create.php, Update.php, Delete.php, or XList.php. For non-CRUD operations, model the endpoint as a property update. For example, updating a team membership status lives at Teams/Http/Memberships/Status/Update.php (PATCH /v1/teams/:teamId/memberships/:membershipId/status).

Register new modules in src/Appwrite/Platform/Appwrite.php. Detailed module guide: src/Appwrite/Platform/AGENTS.md.

Action pattern (HTTP endpoints)

class Create extends Action
{
    public static function getName(): string { return 'createTeam'; }

    public function __construct()
    {
        $this
            ->setHttpMethod(Action::HTTP_REQUEST_METHOD_POST)
            ->setHttpPath('/v1/teams')
            ->desc('Create team')
            ->groups(['api', 'teams'])
            ->label('event', 'teams.[teamId].create')
            ->label('scope', 'teams.write')
            ->param('teamId', '', new CustomId(), 'Team ID.')
            ->param('name', null, new Text(128), 'Team name.')
            ->inject('response')
            ->inject('dbForProject')
            ->inject('queueForEvents')
            ->callback($this->action(...));
    }

    public function action(
        string $teamId,
        string $name,
        Response $response,
        Database $dbForProject,
        Event $queueForEvents,
    ): void {
        // implementation
    }
}

Common injections: $response, $request, $dbForProject, $dbForPlatform, $user, $project, $queueForEvents, $queueForMails, $queueForDeletes.

Conventions

  • PSR-12 formatting enforced by Pint. PSR-4 autoloading.
  • resourceType values are always plural: 'functions', 'sites', 'deployments'.
  • When updating documents, pass only changed attributes as a sparse Document:
    // correct
    $dbForProject->updateDocument('users', $user->getId(), new Document([
        'name' => $name,
    ]));
    // incorrect -- passing full document is inefficient
    $user->setAttribute('name', $name);
    $dbForProject->updateDocument('users', $user->getId(), $user);
    
    Exceptions: migrations, array_merge() with getArrayCopy(), updates where nearly all attributes change, complex nested relationship logic requiring full document state.
  • Avoid introducing dependencies outside the utopia-php ecosystem.
  • Never hardcode credentials -- use environment variables.
  • Code changes may require container restart. No central log location -- check relevant containers.

Patch release process

For bumping patch versions (e.g., 1.9.0 -> 1.9.1), follow the checklist in .claude/skills/patch-release-checklist/SKILL.md. It covers the 4 files that must be updated, console image bumps, CHANGES.md updates, and common pitfalls to avoid.

Cross-repo context

Appwrite is the base server for appwrite/cloud. Changes to the Action pattern, module structure, DI system, or response models affect cloud. The feat-dedicated-db feature spans cloud, edge, and console.