This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
Gallery is a community fork of Immich, a self-hosted photo and video management solution. The fork is currently based on Immich v2.7.5 and regularly rebased onto upstream. Source package names are still immich / immich-web so the rebase path stays clean — only branding, Docker image names, and fork-only code diverge.
Fork-specific features layered on top of upstream include: shared spaces, smart search & filters, user groups, S3-compatible storage, auto-classification, video duplicate detection, pet detection, Google Photos import, image editing & video trimming, and structured JSON logging. See README.md for the full list and docs links.
It's a monorepo managed with pnpm workspaces containing:
- server/ — NestJS 11 backend (TypeScript) — package name:
immich - web/ — SvelteKit frontend with Svelte 5 (TypeScript) — package name:
immich-web - mobile/ — Flutter/Dart app with Riverpod state management
- machine-learning/ — Python FastAPI service (CLIP, facial recognition, OCR, YOLO pet detection via ONNX Runtime)
- cli/ — Node.js CLI (
@immich/cli) - open-api/ — OpenAPI spec and generated SDKs (
@immich/sdk) - e2e/ — End-to-end tests (Playwright + Vitest)
- docs/ — Docusaurus site deployed to
docs.opennoodle.de - branding/ — Fork branding assets and the
apply-brandingscript that rewrites upstream Immich references before Docker builds - deployment/ — Demo, personal, and marketing deploy configs and scripts
make dev # Start full dev stack (Docker Compose)
make dev-update # Rebuild and start dev stack
make e2e # Run E2E test stackmake build-server # Build server (NestJS)
make build-web # Build web (SvelteKit) — depends on SDK
make build-sdk # Build TypeScript SDK
make build-cli # Build CLI — depends on SDK# Server
cd server
pnpm test # Run all unit tests (vitest)
pnpm test -- --run src/services/album.service.spec.ts # Run a single test file
pnpm test:cov # Unit tests with coverage
pnpm test:medium # Medium tests (require DB via Docker)
# Web
cd web
pnpm test # Run all unit tests (vitest)
pnpm test -- --run src/lib/components/MyComponent.spec.ts # Single test file
# E2E
cd e2e
pnpm test # API tests (vitest)
pnpm test:web # Playwright web tests
# Run Playwright against an already-running `make dev` stack on :2283
make e2e-web-dev # web suite
make e2e-web-dev-ui # web suite with Playwright UI
make e2e-api-dev # API tests
make e2e-integration-dev # integration suite# Per-module (from repo root)
make lint-server # ESLint with --fix
make lint-web
make format-server # Prettier --write
make format-web
make check-server # TypeScript type check (tsc --noEmit)
make check-web # svelte-check + tsc --noEmit
# All modules
make lint-all
make format-all
make check-allmake open-api # Regenerate all OpenAPI clients (Dart + TypeScript)
make open-api-typescript # Regenerate TypeScript SDK only
make open-api-dart # Regenerate Dart client only
make sql # Sync SQL query documentation from decorated repositoriespnpm migrations:generate # Auto-generate migration from schema changes
pnpm migrations:run # Apply pending migrations (fresh DB only, see note below)
pnpm migrations:revert # Rollback last migration
pnpm schema:reset # Drop and recreate schema (destructive)Fork migration layout: Gallery maintains two migration directories in source:
server/src/schema/migrations/— upstream Immich migrations (replaced during rebases)server/src/schema/migrations-gallery/— fork-only migrations (never touched by rebases)
How they come together — the postbuild script:
After nest build compiles TypeScript to dist/, the npm postbuild hook (server/package.json) copies dist/schema/migrations-gallery/*.js into dist/schema/migrations/. This means the built dist/schema/migrations/ folder contains ALL migrations (upstream + fork) in one flat directory.
This merge is needed because:
sql-toolsCLI (migrations:run,generate,revert) only reads from one folder (dist/schema/migrations/) and cannot be configured for multiple directories- The server runtime uses
CompositeMigrationProviderwhich reads from bothdist/schema/migrations/anddist/schema/migrations-gallery/— duplicates from the postbuild copy are silently handled viaObject.assign(last folder wins, identical code)
Why two source directories? Keeping fork migrations in migrations-gallery/ means upstream rebases never conflict with fork migration files. The migrations/ directory gets replaced wholesale during rebases, while migrations-gallery/ is untouched.
Runtime migration behavior: DatabaseRepository.createMigrator() uses allowUnorderedMigrations: true so fork migrations with timestamps interleaved between upstream ones apply correctly. This is critical for Immich-to-Gallery migration — users who switch from Immich already have upstream migrations applied, and the fork migrations slot in between them.
pnpm migrations:run uses sql-tools which hardcodes allowUnorderedMigrations: false. This works on fresh databases (CI, initial setup) but will fail on an existing database that already has upstream migrations applied. For existing databases, the server handles migrations automatically on startup via DatabaseRepository.runMigrations().
Adding new fork migrations: Create new migration files in server/src/schema/migrations-gallery/ with a timestamp that doesn't collide with existing migrations. Use round timestamps (e.g., 1775000000000) for easy identification.
- Workers: Three worker types run as separate processes — API (HTTP), Microservices (background jobs), Maintenance
- ORM: Kysely (type-safe SQL query builder, NOT TypeORM). Schema defined in
server/src/schema/tables/ - Job Queue: BullMQ with Redis for async tasks (thumbnails, encoding, ML, etc.)
- Services: All domain services extend
BaseService(insrc/services/base.service.ts) which provides access to ~40 injected repositories - Repositories: Data access layer in
src/repositories/with typed Kysely queries. Methods decorated with@GenerateSqlQueriesget auto-documented - Controllers: HTTP endpoints in
src/controllers/with DTOs insrc/dtos/ - Middleware: Auth guards, error interceptors, file upload handling in
src/middleware/ - Database: PostgreSQL with extensions (pgvectors/vectorchord for embeddings, cube, earthdistance, pg_trgm)
- Testing: Vitest with
newTestService()factory intest/utils.tsfor auto-mocking dependencies. Medium tests use real DB via testcontainers - Fork-only services:
shared-space.service.ts,classification.service.ts,pet-detection.service.ts, and extensions toduplicate.service.ts(video dedup) live alongside upstream services. S3 support is wired throughstorage.service.ts/storage.repository.tswith both disk and S3 backends active simultaneously.
- Svelte 5: Uses
$state,$derived,$effect,$propsrunes in newer code. Older code uses Svelte stores - Component library:
@immich/uifor shared UI primitives - State management patterns:
- Managers (
src/lib/managers/): Class-based singletons using Svelte 5 runes for business logic - Stores (
src/lib/stores/): Mix of Svelte writable stores and persisted stores
- Managers (
- API client: Generated
@immich/sdkwrapping fetch calls - Real-time: Socket.IO client for server events
- Testing: Vitest + @testing-library/svelte with happy-dom
- Styling: Tailwind CSS 4 with
@immich/uitheme system
- State management: Riverpod (hooks_riverpod)
- Local DB: Isar with Drift for migrations
- API client: Generated from OpenAPI (in
mobile/openapi/) - Navigation: auto_route
- Framework: FastAPI with Gunicorn/Uvicorn
- Models: ONNX Runtime inference (CLIP, InsightFace, RapidOCR)
- Model management: Hugging Face Hub with local caching
- Package manager: uv
- Formatting: Prettier with 120 char line width, single quotes, trailing commas, semicolons
- Imports: Auto-organized by
prettier-plugin-organize-imports - Linting: ESLint with zero warnings policy (
--max-warnings 0) - Server imports: No relative imports allowed — use
src/path alias - TypeScript: Strict mode in all packages
- Async:
no-floating-promisesandno-misused-promisesenforced everywhere
When server API endpoints change:
- Build server:
cd server && pnpm build - Regenerate specs:
pnpm sync:open-api - Regenerate clients:
make open-api(generates both TypeScript SDK and Dart client)
The TypeScript SDK uses oazapfts for generation. The Dart client uses OpenAPI Generator with custom mustache templates and patches (Java required — see feedback_openapi_dart_generation).
Upstream Immich references are rewritten to Gallery at build time by branding/apply-branding.sh. This runs automatically inside the Dockerfiles before nest build / the web build, so local pnpm dev and make dev keep upstream names in source. Do not commit branded output: edit the original Immich references and let the script rewrite them during Docker builds. Skipping apply-branding before a Docker build will leak upstream names into deployed artifacts.
- Release workflows (manual
workflow_dispatch, triggered frommain): mobile and server release independently — no draft handoff, no auto-versioning (versions are always supplied manually).- Release Mobile (
.github/workflows/gallery-release-mobile.yml): takes a requiredversion, builds + signs the Android AAB/APK and iOS IPA, uploads the AAB to Play internal and the IPA to TestFlight, keeps the APK as a workflow artifact, and records the built commit SHA in the run summary. Creates no GitHub Release or git tag. - Release Gallery Server (
.github/workflows/gallery-release-server-only.yml): takes a requiredversionand an optionalcommit(defaults to branch HEAD; pass the SHA the mobile run recorded to ship a matching build). Builds + pushesgallery-server/gallery-ml/gallery-ml:*-cuda, moves thevX.Y.Z/vX/releasetags, creates the GitHub Release, and flips the version endpoint self-hosted instances poll. Seedocs/plans/2026-05-18-decoupled-release-design.md.
- Release Mobile (
- Deploy targets:
demo.opennoodle.de(demo),docs.opennoodle.de(Docusaurus). Each has a corresponding skill in.claude/skills/(see/deploy-gallery-*slash commands). - RC builds:
rc-personalskill ships a tagged server image to the personal instance via a compose override — remember to remove the override after merge or release deploys will ship stale RC images.
CONTRIBUTING.mdand the README's Contributing section cover the dev-environment setup (cp docker/example.env docker/.env,pnpm install,make dev).- User-facing docs live in
docs/docs/and are deployed todocs.opennoodle.de. Run prettier on any markdown underdocs/ordocs/plans/before committing — CI Docs Build is strict. - Guides for switching to / from Gallery live under
docs/docs/guides/— the switch-back-to-immich script is atscripts/revert-to-immich/.