This document gives contributors a fast map of Maintainerr's architecture. It is tool-neutral and should support manual development, IDE workflows, and automation equally. It is intentionally high level: use it to find the right area of the codebase, then follow the local module, tests, and contracts for exact behaviour.
Refresh this file when adding or removing top-level workspaces, server modules, global UI providers, integration surfaces, persistence flows, or deployment entry points.
Maintainerr is a TypeScript monorepo managed with Turborepo and Yarn workspaces.
Maintainerr/
|-- apps/
| |-- server/ # Nest API, jobs, integrations, persistence, logs
| `-- ui/ # Vite React UI, routes, client API calls
|-- packages/
| `-- contracts/ # Shared DTOs, Zod schemas, enums, and types
|-- docs/ # Feature-level technical notes
|-- docker/ # Docker helper configuration
|-- tools/ # Release and maintenance scripts
| `-- dev/ # Local dev mocks (fake Plex/Jellyfin) and DB seed
|-- .codex/config.toml # Codex project MCP server config
|-- .mcp.json # Claude Code project MCP server config
|-- .vscode/mcp.json # VS Code MCP server config mirror
|-- Dockerfile # Multi-stage production image
|-- README.md # Product overview and installation entry point
|-- CONTRIBUTING.md # Contributor setup and process
`-- turbo.json # Workspace task graph
flowchart LR
User["Browser user"] --> UI["React UI<br/>apps/ui"]
UI -->|"Axios REST calls"| API["Nest API<br/>apps/server"]
API -->|"SSE /api/events/stream"| UI
API --> DB[("SQLite<br/>maintainerr.sqlite")]
API --> Data[("Data directory<br/>logs, posters, overlays")]
API --> Media["Media-server abstraction"]
Media --> Plex["Plex adapter"]
Media --> Jellyfin["Jellyfin adapter"]
Media --> Emby["Emby adapter"]
API --> Servarr["Radarr / Sonarr"]
API --> Seerr["Seerr"]
API --> Tautulli["Tautulli"]
API --> Streamystats["Streamystats"]
API --> Metadata["TMDB / TVDB / Sportarr"]
API --> GitHub["GitHub releases"]
In production, the Nest server also serves the built UI from
apps/server/dist/ui. In development, Vite and Nest run through the workspace
yarn dev task.
apps/ui owns client routing, UI state, and API calls. It uses Vite, React,
React Router, TanStack Query, React Hook Form, TailwindCSS, and Headless UI.
src/router.tsxis the route source of truth. It uses eager shell routes and lazy feature pages with preload support.src/main.tsxwires the global provider stack:QueryClientProvider -> EventsProvider -> TaskStatusProvider -> SearchContextProvider -> RouterProvider.src/utils/ApiHandler.tsxcentralises API base path handling and Axios REST helpers.src/contexts/events-context.tsxopens the reconnecting SSE stream and exportsuseEventfor typed feature subscriptions.src/contexts/taskstatus-context.tsxcombines initial task status queries with live rule and collection handler events.src/contexts/search-context.tsxowns global search text shared across searchable list views.src/components/Common/contains shared UI primitives. Prefer existing buttons, loading boundaries, modals, tables, and feedback patterns before adding new ones.
apps/server owns the Nest application, background jobs, persistence, external
integrations, and production static serving.
src/main.tsbootstraps Nest, validates the data directory, configures base path handling, Swagger, CORS, logging, and graceful shutdown.src/app/app.module.tswires TypeORM, events, static serving, and feature modules.src/modules/settings/stores user configuration and coordinates media server switching.src/modules/api/media-server/provides the server-agnostic media server interface, factory, controller, and shared utilities.src/modules/api/media-server/plex/,src/modules/api/media-server/jellyfin/, andsrc/modules/api/media-server/emby/contain server-specific adapters, constants, mappers, caching, and SDK/API calls.- Other
src/modules/api/submodules wrap integration clients and helper APIs, including Plex legacy routes, Servarr, Seerr, Tautulli, Streamystats, TMDB, TVDB, the Sportarr metadata API, GitHub, external API, internal API, and shared request/cache helpers. src/modules/rules/evaluates rule groups against media-server and external service data.src/modules/collections/tracks matched media, exclusions, collection logs, posters, and collection handling actions.src/modules/actions/contains the Radarr, Sonarr and Sportarr action handlers for destructive or state-changing Servarr actions such as delete, unmonitor, and quality profile changes, plus the opt-in leftover-folder cleanup - the one place in the module that writes to the local filesystem.src/modules/tasks/creates and tracks scheduled jobs.src/modules/telemetry/builds and sends the anonymous weekly usage report, scheduled on a per-instance jitter slot.src/modules/events/exposes server-sent events for rule and collection job progress.src/modules/version/resolves the running build against GitHub, for the version indicator and the update-available notification.src/modules/overlays/,src/modules/metadata/,src/modules/notifications/,src/modules/logging/, andsrc/modules/storage-metrics/own their respective feature areas.
packages/contracts owns shared DTOs, Zod schemas, enums, and cross-package
types. Add request/response shapes here only when they are deliberately shared
between the UI and server. Keep new contracts minimal and validate external
input at system boundaries.
Maintainerr stores local state in SQLite through TypeORM.
- Development database:
data/maintainerr.sqlite. - Production database:
${DATA_DIR}/maintainerr.sqlite, defaulting to/opt/data/maintainerr.sqlite. - TypeORM loads entities with
autoLoadEntitiesand runs migrations automatically on server start. - Migration files live in
apps/server/src/database/migrations/. apps/server/src/datasource-config.tsis the TypeORM CLI data source used for migration generation and execution.- Schema changes must use the TypeORM workflow in typeorm_instructions.txt; do not hand-write SQL migrations.
The data directory also stores operational files such as logs, custom
collection posters, overlay images, original artwork backups, and other
runtime assets. The Docker image exposes /opt/data as a volume.
Maintainerr integrates with:
- Plex, Jellyfin, and Emby through the media-server abstraction.
- Radarr and Sonarr for unmonitoring, deleting, and quality profile actions.
- Seerr-compatible services for request cleanup.
- Tautulli for Plex analytics and rule data.
- Streamystats for Jellyfin item-level analytics surfaced on the media modal. Authentication reuses the configured Jellyfin API key. Emby is not supported upstream.
- TMDB and TVDB for metadata resolution, and the Sportarr metadata API for
Sportarr leagues, which answers ahead of the primary provider for a show
that carries a Sportarr id. It reads the configured Sportarr connections,
and sportarr.net only when
SPORTARR_NET=onasks for it. - GitHub for release/version checks.
- The Maintainerr telemetry collector for the anonymous weekly usage report, when it is enabled (see the Telemetry section of the README).
- Notification providers such as Discord, Slack, Telegram, Pushover, Gotify, ntfy, email, Pushbullet, LunaSea, and webhooks.
When changing an external integration, confirm current behaviour from the official API documentation before coding. Keep third-party secrets in settings or environment variables; never hard-code tokens or keys.
The production Docker image builds all workspaces, focuses production
dependencies, copies the UI build into the server distribution, and starts the
Nest server through docker/start.sh.
Important runtime environment variables include:
DATA_DIR: production data directory, default/opt/data.UI_PORT: HTTP listen port, default6246.UI_HOSTNAME: HTTP bind host, default0.0.0.0.BASE_PATH: optional subdirectory mount path for both API and UI serving.GITHUB_TOKEN: optional token for higher GitHub API rate limits.TELEMETRY: set tooffto disable the anonymous weekly usage report, whatever the stored setting says.SPORTARR_NET: set toonto read Sportarr league artwork and descriptions from sportarr.net for a league none of the configured Sportarr connections tracks. Off by default, so an install without Sportarr never calls it.VERSION_TAGandGIT_SHA: release metadata surfaced by the app.LOG_LEVEL: optional process-local log level override; recognised values take precedence over the saved setting without writing the database.DEBUG: influences default log level during migration seeding.
The server exposes health probes under /api/health: /live is process-only,
/ready checks database readiness, and /api/health mirrors readiness. The
Docker image runs /opt/app/healthcheck.sh, normalises BASE_PATH, and probes
/api/health/ready on the local UI_PORT.
- Treat configured integration tokens and API keys as secrets.
- Keep user-provided URLs and external API responses validated or normalised at system boundaries.
- Prefer typed DTOs and Zod schemas for request/response data.
- Avoid logging raw secrets. Use existing secret masking helpers where available.
- Keep destructive actions explicit: delete, unmonitor, and quality-profile changes should remain tied to collection/rule configuration and action handlers.
Run workspace commands from the repository root.
yarn install
yarn dev
yarn lint
yarn check-types
yarn test
yarn buildClosest quality gates should run in this order where applicable: lint, typecheck, tests, then build. For doc-only changes, a targeted Prettier check is usually enough.
GitHub CI mirrors the root workspace workflow on Node.js 26: the formatting
and TypeScript lint jobs run corepack install, corepack enable, and
yarn --immutable, while the quality workflow also includes a separate
yaml-lint job running yamllint -s .; the test workflow builds packages/*
before yarn turbo test. The development image workflow builds the production
Dockerfile for linux/amd64 and linux/arm64.
Testing conventions:
- Server tests use Jest, with
*.spec.tsfiles near the code underapps/server/src. - UI tests use Vitest and React Testing Library.
- Contracts use TypeScript checks and package-level linting.
- Project MCP server config lives in
.codex/config.toml,.mcp.json, and.vscode/mcp.json; keep them in sync. The GitHub MCP server is read-only, and Playwright screenshots should be saved under.playwright-mcp/. - End-to-end checks of media-server-dependent flows use the dev mocks and DB
seed under
tools/dev/(fake-plex.mjs/fake-jellyfin.mjs+seed-db.mjs) to drive the UI with Playwright against deterministic data; seeAGENTS.mdfor the workflow.
See CONTRIBUTING.md for setup, branching, and pull request expectations.
- Keep
modules/api/media-server/server-agnostic. The shared interface, factory, controller, and utilities must not import Plex or Jellyfin types. - Put server-specific logic under the matching adapter directory (
plex/,jellyfin/, oremby/). - Use
supportsFeature()for conditional media-server capabilities. - Implement every new media-server interface method for all supported media servers. Put partial support behind feature checks, not optional interface holes.
- Keep mappers focused on type conversion, not business decisions.
- Prefer shared settings feedback, loading, and button components in the UI.
- Avoid layout shift in shell and settings flows.
- Preserve established rule
nameandhumanNameconventions across media servers. - Keep migrations safe, reversible, and generated through TypeORM.
Feature documentation lives in the Maintainerr_docs repository and is published
at https://docs.maintainerr.info. It is the single source of truth: describe a
feature there, not here, so the two cannot drift. This repository documents
architecture and intent only; for how the code works, read the code.
- https://docs.maintainerr.info/collections/ covers custom collection posters and the per-collection opt-in post-delete folder cleanup, including which *arr actions strand a folder, the same-path mount requirement, and the guardrails.
- https://docs.maintainerr.info/overlays/ covers overlay templates, settings, and processing behaviour.
README.mddescribes product capabilities, installation, API compatibility, and supported services.
- Arr: Shorthand for Radarr and Sonarr.
- Collection handler: Background logic that applies configured actions to media after it has spent the configured duration in a Maintainerr collection.
- Contracts: Shared package containing DTOs, schemas, enums, and types used by both UI and server.
- Data directory: Runtime directory containing the SQLite database and local files such as logs, posters, and overlays.
- Media-server abstraction: Server-side interface that lets Maintainerr support Plex, Jellyfin, and Emby without leaking their implementation details into shared code.
- Rule group: A configured set of rules that selects media and links it to a Maintainerr collection.
- Seerr: The request-management integration (github.com/seerr-team/seerr) used for request cleanup.
- SSE: Server-sent events used for live rule and collection job updates.