CodeVetter · Repo Unpack · Extension points
immich-app/immich
The codebase is organized around explicit registries: controllers/index.ts, services/index.ts, repositories/index.ts, commands/index.ts, plus decorator-driven registration (@OnJob, @OnEvent, @Endpoint, @Authenticated). Adding an endpoint means a controller method + zod DTO + index registration; adding a job means enum entries + @OnJob handler (enforced at startup). A WASM plugin system via @immich/plugin-sdk and workflows provide runtime extensibility, and the OpenAPI→SDK codegen pipeline propagates API changes to all clients.
- New HTTP endpoints plug in via a controller class added to the controllers array in controllers/index.ts; the ApiModule mounts every entry under the global /api prefix.
- New background jobs require: a JobName enum member, a QueueName choice, and a method decorated @OnJob({name, queue}) on a service — jobRepository.setup validates complete coverage at boot and throws ImmichStartupError for missing or duplicate handlers.
- New domain events plug in via the typed EventMap in event.repository.ts plus @OnEvent({name, priority, server, workers}) on service methods; BootstrapEventPriority documents ordering constraints for bootstrap handlers.
- New services extend BaseService (which injects all ~55 repositories) and must be added to services/index.ts so queueService.setServices and eventRepository.setup can discover their decorated methods.
- New repositories are classes added to repositories/index.ts and BASE_SERVICE_DEPENDENCIES so every service receives them positionally through BaseService's constructor.
- WASM plugins are a first-class extension point: authors build against @immich/plugin-sdk (host-functions.ts, sdk.ts), plugins install under IMMICH_PLUGINS_INSTALL_FOLDER, are pooled/executed by PluginRepository via extism, and their methods/templates surface through plugin.controller and workflow compatibility checks.
- Admin CLI commands plug in via nest-commander Command classes registered in commands/index.ts (commandsAndQuestions) and run under ImmichAdminModule when invoked as 'immich-admin'.
- API versioning/documentation is decorator-driven: @Endpoint({summary, history: new HistoryBuilder().added('vX').beta('vY')}) emits custom swagger extensions (x-immich-history/state) that feed the generated spec — deprecations flow automatically via ApiTag.Deprecated.
- Request/response validation is nestjs-zod: DTOs are zod schemas (e.g., EnvSchema, QueueNameSchema) validated globally by ZodValidationPipe and serialized by ZodSerializerInterceptor registered as APP_PIPE/APP_INTERCEPTOR.
- DB schema changes plug in through sql-tools: table classes in server/src/schema/tables with Column/trigger decorators (PrimaryGeneratedUuidV7Column, UpdatedAtTrigger), functions in schema/functions.ts, then 'pnpm migrations:generate' diffs into schema/migrations with an ORDER file.
- Client surface extends via codegen: edit DTOs/controllers → run sync-open-api.ts to refresh immich-openapi-specs.json → mise task open-api-typescript regenerates packages/sdk fetch-client used by web/cli/e2e; mobile consumes a generated dart openapi package.
- Web features plug in as SvelteKit routes under web/src/routes/(user)/... with shared components/stores/managers in web/src/lib, all typed against @immich/sdk.
- Bull queues self-register: ConfigRepository builds RegisterQueueOptions for every QueueName value (bull.queues) so adding a QueueName enum member automatically creates the queue without extra wiring. (inferred)