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-*, plusdoctor,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.
resourceTypevalues are always plural:'functions','sites','deployments'.- When updating documents, pass only changed attributes as a sparse Document:
Exceptions: migrations,// 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);array_merge()withgetArrayCopy(), updates where nearly all attributes change, complex nested relationship logic requiring full document state. - Avoid introducing dependencies outside the
utopia-phpecosystem. - 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.