Homarr Codebase Context
Homarr is an open-source, self-hosted dashboard for managing homelab services. It integrates with 50+ self-hosted apps (media servers, download clients, DNS, NAS, etc.) and provides a drag-and-drop widget-based UI.
Tech Stack
- Monorepo: pnpm workspaces + Turborepo
- Framework: Next.js (App Router,
output: "standalone") — T3 Stack
- Language: TypeScript throughout
- ORM: Drizzle (supports SQLite via better-sqlite3, MySQL, PostgreSQL)
- API: tRPC (HTTP + WebSocket subscriptions) with superjson + OpenAPI bridge via
trpc-to-openapi
- Auth: NextAuth v5 (database sessions, Credentials/LDAP/OIDC providers)
- UI: Mantine v9 (not Tailwind) + Tabler icons
- State: Jotai (atoms), TanStack Query (server state via tRPC)
- Realtime: WebSocket server (
ws) for tRPC subscriptions, backed by Redis pub/sub
- Cron:
node-cron in a standalone Fastify service (apps/tasks)
- i18n: next-intl with
[locale] dynamic segment (prefix mode: "never", locale from cookie)
- Testing: Vitest + jsdom, Playwright for E2E
- Lint/Format: oxlint + oxfmt (not ESLint/Prettier)
- Docs: Docusaurus 3 in
apps/docs/ (@homarr/docs)
- Package manager: pnpm 10.34.1, Node >= 24.16.0
Repository Structure
homarr/
├── apps/
│ ├── nextjs/ # Main Next.js application (port 3000)
│ ├── docs/ # Docusaurus 3 documentation site (@homarr/docs)
│ ├── tasks/ # Cron job runner + Fastify tRPC API (port 3002)
│ └── websocket/ # Standalone tRPC WebSocket server (port 3001)
├── packages/
│ ├── api/ # tRPC appRouter, procedures, OpenAPI
│ ├── auth/ # NextAuth config, providers, session, API keys
│ ├── db/ # Drizzle schema (3 DB drivers), migrations, queries
│ ├── core/ # Env validation, DB/Redis driver factories, logging
│ ├── definitions/ # Domain enums: WidgetKind, IntegrationKind, permissions
│ ├── widgets/ # All 39 dashboard widgets (definitions + components)
│ ├── integrations/ # Integration classes (HTTP clients to external apps)
│ ├── redis/ # Redis pub/sub channels, caching abstractions
│ ├── translation/ # next-intl setup, locale configs, lang JSON files
│ ├── ui/ # Shared Mantine components, theme, hooks
│ ├── validation/ # Shared zod schemas for API/forms
│ ├── common/ # Shared utilities, IDs, errors
│ ├── cron-jobs/ # Cron job implementations (25+ jobs)
│ ├── cron-jobs-core/ # Cron scheduling primitives
│ ├── cron-job-api/ # tRPC router for job management (start/stop/trigger)
│ ├── cron-job-status/ # Cron status via Redis
│ ├── boards/ # Board context, edit mode, cache updater
│ ├── modals/ # Modal primitives on Mantine
│ ├── modals-collection/ # Feature modals (apps, boards, docker, etc.)
│ ├── form/ # useZodForm (Mantine + zod resolver)
│ ├── forms-collection/# Reusable form UIs (new app, icon picker, upload)
│ ├── spotlight/ # Command palette / search with multiple modes
│ ├── request-handler/ # Server request handlers (feeds, integrations)
│ ├── notifications/ # Mantine notifications wrapper
│ ├── docker/ # Dockerode-based Docker access
│ ├── icons/ # Icon DB/repo integration
│ ├── image-proxy/ # Image proxy + caching
│ ├── ping/ # Reachability / ping utilities
│ ├── analytics/ # Server-side analytics (Umami)
│ ├── server-settings/ # Server setting keys/types
│ ├── settings/ # User-facing settings UI context
│ └── cli/ # Node CLI for ops (brocli)
├── tooling/
│ ├── typescript/ # Base tsconfig
│ └── github/ # CI setup action
├── development/ # Dev docker-compose (Redis, MySQL, PostgreSQL)
├── e2e/ # E2E test specs
└── Dockerfile # Multi-stage production build
Three Runtime Services
| Service |
Port |
Role |
| Next.js |
3000 |
Main web app (SSR + API routes) |
| WebSocket |
3001 |
tRPC subscriptions via ws |
| Tasks |
3002 |
Cron job runner + management API via Fastify |
In Docker, nginx listens on 7575 and proxies to all three. The tasks service authenticates via CRON_JOB_API_KEY header.
Package Dependency Layers
- Foundation:
core (env, DB drivers, Redis client, logging) → no @homarr deps
- Domain:
definitions → common → core
- Data:
db → core, definitions, common
- Auth:
auth → db, definitions, core, validation
- Cross-cutting:
translation, validation, redis, server-settings
- Feature backends:
integrations, request-handler, cron-jobs, docker, ping
- API surface:
api → pulls in auth, db, redis, integrations, etc.
- UI stack:
ui, modals, form, notifications, boards, spotlight
- Composed features:
widgets, modals-collection, forms-collection
Key Patterns
tRPC
- Server caller (RSC):
import { api } from "@homarr/api/server" → cached caller with auth context
- Client hooks:
import { clientApi } from "@homarr/api/client" → React Query hooks
- Router structure:
packages/api/src/root.ts defines top-level namespaces (user, board, widget, integration, docker, kubernetes, etc.)
- Procedures:
publicProcedure, protectedProcedure, permissionRequiredProcedure, onboardingProcedure
- WebSocket subscriptions: Client uses
wsLink when type === "subscription", connects to port 3001
Multi-Database Support
Drizzle schemas exist in three parallel implementations:
packages/db/schema/sqlite.ts
packages/db/schema/mysql.ts
packages/db/schema/postgresql.ts
Selected at runtime via DB_DRIVER env (better-sqlite3 | mysql2 | node-postgres). Migrations are per-engine in packages/db/migrations/{sqlite,mysql,postgresql}/.
Widget System
Widgets are registered in packages/widgets/src/index.tsx as widgetImports (must satisfy Record<WidgetKind, ...>). Each widget:
- Has a
definition created via createWidgetDefinition(kind, { icon, createOptions, supportedIntegrations? })
- Uses
optionsBuilder.from(factory => ({ ... })) for typed options
- Loads dynamically via
next/dynamic
- Fetches data via tRPC client hooks (backed by cron job caches)
Integration System
Integrations are defined in packages/definitions/src/integration.ts (integrationDefs) and instantiated via createIntegrationAsync in packages/integrations/src/base/creator.ts. Each extends abstract Integration class with testConnectionAsync and typed methods.
Cron Job System
Jobs defined in packages/cron-jobs/src/jobs/. Each job:
- Uses
createCronJob with a cron expression
- Publishes results to Redis channels
- Widget subscriptions consume those channels via tRPC subscriptions
- Job management (start/stop/trigger) via
cronJobApi HTTP client to port 3002
Modals
Created with createModal(component).withOptions({ defaultTitle }) from @homarr/modals. Opened via useModalAction(SomeModal). Feature modals live in packages/modals-collection/.
Forms
useZodForm from @homarr/form wraps Mantine useForm with zod validation. Shared form UIs in packages/forms-collection/.
Routing (App Router)
apps/nextjs/src/app/[locale]/ — all UI routes under locale segment
- Route groups:
(home), (content), (board) — don't affect URLs
- Key routes:
boards/[name], manage/... (admin), auth/login, init (onboarding), widgets/[kind]
- Locale driven by cookie (no URL prefix)
Auth Providers
Configured via AUTH_PROVIDERS env. Supports: credentials (local), ldap, oidc. Database sessions with httpOnly cookies. API key auth for programmatic access.
Environment Variables
Key env vars (from .env.example):
DB_DRIVER: better-sqlite3 | mysql2 | node-postgres
DB_URL: Database connection string
AUTH_SECRET: NextAuth secret
SECRET_ENCRYPTION_KEY: AES-256-CBC for integration secrets
AUTH_PROVIDERS: Comma-separated (credentials, ldap, oidc)
CRON_JOB_API_KEY: Shared key between Next.js and tasks service
REDIS_*: Redis connection (host, port, password)
ENABLE_KUBERNETES: Optional Kubernetes integration
Development Commands
pnpm dev — runs all three services in parallel via Turborepo
pnpm docker:dev — starts Redis + MySQL + PostgreSQL containers
pnpm db:push — push schema to SQLite (dev)
pnpm db:studio — Drizzle Studio
pnpm test — Vitest unit tests
pnpm test:e2e — Playwright E2E
pnpm lint / pnpm format — oxlint + oxfmt
pnpm dev:docs — Docusaurus docs site (port 3003)
pnpm typecheck — tsc --noEmit across all packages
Conventions
- Workspace packages use
@homarr/ scope
- All versions managed via pnpm
catalog: in workspace
- Path alias
~/* → ./src/* in Next.js app
- Mantine for all UI (no Tailwind) — primary color is red,
autoContrast: true
- Icons from
@tabler/icons-react
- Drag-and-drop via
@dnd-kit/*
typescript.ignoreBuildErrors: true in next.config (types checked separately via typecheck)
serverExternalPackages: dockerode, isomorphic-dompurify, jsdom
1---2name: codebase-context3description: Comprehensive Homarr codebase architecture and conventions reference. Use when orienting in the monorepo, understanding package dependency layers, the three runtime services and ports, tRPC router structure, multi-database support, widget/integration/cron system architecture, routing, auth providers, env vars, or key patterns before writing code.4---56# Homarr Codebase Context78Homarr is an open-source, self-hosted dashboard for managing homelab services. It integrates with 50+ self-hosted apps (media servers, download clients, DNS, NAS, etc.) and provides a drag-and-drop widget-based UI.910## Tech Stack1112- **Monorepo**: pnpm workspaces + Turborepo13- **Framework**: Next.js (App Router, `output: "standalone"`) — T3 Stack14- **Language**: TypeScript throughout15- **ORM**: Drizzle (supports SQLite via better-sqlite3, MySQL, PostgreSQL)16- **API**: tRPC (HTTP + WebSocket subscriptions) with superjson + OpenAPI bridge via `trpc-to-openapi`17- **Auth**: NextAuth v5 (database sessions, Credentials/LDAP/OIDC providers)18- **UI**: Mantine v9 (not Tailwind) + Tabler icons19- **State**: Jotai (atoms), TanStack Query (server state via tRPC)20- **Realtime**: WebSocket server (`ws`) for tRPC subscriptions, backed by Redis pub/sub21- **Cron**: `node-cron` in a standalone Fastify service (`apps/tasks`)22- **i18n**: next-intl with `[locale]` dynamic segment (prefix mode: `"never"`, locale from cookie)23- **Testing**: Vitest + jsdom, Playwright for E2E24- **Lint/Format**: oxlint + oxfmt (not ESLint/Prettier)25- **Docs**: Docusaurus 3 in `apps/docs/` (`@homarr/docs`)26- **Package manager**: pnpm 10.34.1, Node >= 24.16.02728## Repository Structure2930```text31homarr/32├── apps/33│ ├── nextjs/ # Main Next.js application (port 3000)34│ ├── docs/ # Docusaurus 3 documentation site (@homarr/docs)35│ ├── tasks/ # Cron job runner + Fastify tRPC API (port 3002)36│ └── websocket/ # Standalone tRPC WebSocket server (port 3001)37├── packages/38│ ├── api/ # tRPC appRouter, procedures, OpenAPI39│ ├── auth/ # NextAuth config, providers, session, API keys40│ ├── db/ # Drizzle schema (3 DB drivers), migrations, queries41│ ├── core/ # Env validation, DB/Redis driver factories, logging42│ ├── definitions/ # Domain enums: WidgetKind, IntegrationKind, permissions43│ ├── widgets/ # All 39 dashboard widgets (definitions + components)44│ ├── integrations/ # Integration classes (HTTP clients to external apps)45│ ├── redis/ # Redis pub/sub channels, caching abstractions46│ ├── translation/ # next-intl setup, locale configs, lang JSON files47│ ├── ui/ # Shared Mantine components, theme, hooks48│ ├── validation/ # Shared zod schemas for API/forms49│ ├── common/ # Shared utilities, IDs, errors50│ ├── cron-jobs/ # Cron job implementations (25+ jobs)51│ ├── cron-jobs-core/ # Cron scheduling primitives52│ ├── cron-job-api/ # tRPC router for job management (start/stop/trigger)53│ ├── cron-job-status/ # Cron status via Redis54│ ├── boards/ # Board context, edit mode, cache updater55│ ├── modals/ # Modal primitives on Mantine56│ ├── modals-collection/ # Feature modals (apps, boards, docker, etc.)57│ ├── form/ # useZodForm (Mantine + zod resolver)58│ ├── forms-collection/# Reusable form UIs (new app, icon picker, upload)59│ ├── spotlight/ # Command palette / search with multiple modes60│ ├── request-handler/ # Server request handlers (feeds, integrations)61│ ├── notifications/ # Mantine notifications wrapper62│ ├── docker/ # Dockerode-based Docker access63│ ├── icons/ # Icon DB/repo integration64│ ├── image-proxy/ # Image proxy + caching65│ ├── ping/ # Reachability / ping utilities66│ ├── analytics/ # Server-side analytics (Umami)67│ ├── server-settings/ # Server setting keys/types68│ ├── settings/ # User-facing settings UI context69│ └── cli/ # Node CLI for ops (brocli)70├── tooling/71│ ├── typescript/ # Base tsconfig72│ └── github/ # CI setup action73├── development/ # Dev docker-compose (Redis, MySQL, PostgreSQL)74├── e2e/ # E2E test specs75└── Dockerfile # Multi-stage production build76```7778## Three Runtime Services7980| Service | Port | Role |81| --------- | ---- | -------------------------------------------- |82| Next.js | 3000 | Main web app (SSR + API routes) |83| WebSocket | 3001 | tRPC subscriptions via `ws` |84| Tasks | 3002 | Cron job runner + management API via Fastify |8586In Docker, nginx listens on **7575** and proxies to all three. The tasks service authenticates via `CRON_JOB_API_KEY` header.8788## Package Dependency Layers89901. **Foundation**: `core` (env, DB drivers, Redis client, logging) → no `@homarr` deps912. **Domain**: `definitions` → `common` → `core`923. **Data**: `db` → `core`, `definitions`, `common`934. **Auth**: `auth` → `db`, `definitions`, `core`, `validation`945. **Cross-cutting**: `translation`, `validation`, `redis`, `server-settings`956. **Feature backends**: `integrations`, `request-handler`, `cron-jobs`, `docker`, `ping`967. **API surface**: `api` → pulls in auth, db, redis, integrations, etc.978. **UI stack**: `ui`, `modals`, `form`, `notifications`, `boards`, `spotlight`989. **Composed features**: `widgets`, `modals-collection`, `forms-collection`99100## Key Patterns101102### tRPC103104- **Server caller** (RSC): `import { api } from "@homarr/api/server"` → cached caller with auth context105- **Client hooks**: `import { clientApi } from "@homarr/api/client"` → React Query hooks106- **Router structure**: `packages/api/src/root.ts` defines top-level namespaces (`user`, `board`, `widget`, `integration`, `docker`, `kubernetes`, etc.)107- **Procedures**: `publicProcedure`, `protectedProcedure`, `permissionRequiredProcedure`, `onboardingProcedure`108- **WebSocket subscriptions**: Client uses `wsLink` when `type === "subscription"`, connects to port 3001109110### Multi-Database Support111112Drizzle schemas exist in three parallel implementations:113114- `packages/db/schema/sqlite.ts`115- `packages/db/schema/mysql.ts`116- `packages/db/schema/postgresql.ts`117118Selected at runtime via `DB_DRIVER` env (`better-sqlite3` | `mysql2` | `node-postgres`). Migrations are per-engine in `packages/db/migrations/{sqlite,mysql,postgresql}/`.119120### Widget System121122Widgets are registered in `packages/widgets/src/index.tsx` as `widgetImports` (must satisfy `Record<WidgetKind, ...>`). Each widget:123124- Has a `definition` created via `createWidgetDefinition(kind, { icon, createOptions, supportedIntegrations? })`125- Uses `optionsBuilder.from(factory => ({ ... }))` for typed options126- Loads dynamically via `next/dynamic`127- Fetches data via tRPC client hooks (backed by cron job caches)128129### Integration System130131Integrations are defined in `packages/definitions/src/integration.ts` (`integrationDefs`) and instantiated via `createIntegrationAsync` in `packages/integrations/src/base/creator.ts`. Each extends abstract `Integration` class with `testConnectionAsync` and typed methods.132133### Cron Job System134135Jobs defined in `packages/cron-jobs/src/jobs/`. Each job:136137- Uses `createCronJob` with a cron expression138- Publishes results to Redis channels139- Widget subscriptions consume those channels via tRPC subscriptions140- Job management (start/stop/trigger) via `cronJobApi` HTTP client to port 3002141142### Modals143144Created with `createModal(component).withOptions({ defaultTitle })` from `@homarr/modals`. Opened via `useModalAction(SomeModal)`. Feature modals live in `packages/modals-collection/`.145146### Forms147148`useZodForm` from `@homarr/form` wraps Mantine `useForm` with zod validation. Shared form UIs in `packages/forms-collection/`.149150### Routing (App Router)151152- `apps/nextjs/src/app/[locale]/` — all UI routes under locale segment153- Route groups: `(home)`, `(content)`, `(board)` — don't affect URLs154- Key routes: `boards/[name]`, `manage/...` (admin), `auth/login`, `init` (onboarding), `widgets/[kind]`155- Locale driven by cookie (no URL prefix)156157### Auth Providers158159Configured via `AUTH_PROVIDERS` env. Supports: `credentials` (local), `ldap`, `oidc`. Database sessions with httpOnly cookies. API key auth for programmatic access.160161## Environment Variables162163Key env vars (from `.env.example`):164165- `DB_DRIVER`: `better-sqlite3` | `mysql2` | `node-postgres`166- `DB_URL`: Database connection string167- `AUTH_SECRET`: NextAuth secret168- `SECRET_ENCRYPTION_KEY`: AES-256-CBC for integration secrets169- `AUTH_PROVIDERS`: Comma-separated (`credentials`, `ldap`, `oidc`)170- `CRON_JOB_API_KEY`: Shared key between Next.js and tasks service171- `REDIS_*`: Redis connection (host, port, password)172- `ENABLE_KUBERNETES`: Optional Kubernetes integration173174## Development Commands175176- `pnpm dev` — runs all three services in parallel via Turborepo177- `pnpm docker:dev` — starts Redis + MySQL + PostgreSQL containers178- `pnpm db:push` — push schema to SQLite (dev)179- `pnpm db:studio` — Drizzle Studio180- `pnpm test` — Vitest unit tests181- `pnpm test:e2e` — Playwright E2E182- `pnpm lint` / `pnpm format` — oxlint + oxfmt183- `pnpm dev:docs` — Docusaurus docs site (port 3003)184- `pnpm typecheck` — tsc --noEmit across all packages185186## Conventions187188- Workspace packages use `@homarr/` scope189- All versions managed via pnpm `catalog:` in workspace190- Path alias `~/*` → `./src/*` in Next.js app191- Mantine for all UI (no Tailwind) — primary color is red, `autoContrast: true`192- Icons from `@tabler/icons-react`193- Drag-and-drop via `@dnd-kit/*`194- `typescript.ignoreBuildErrors: true` in next.config (types checked separately via `typecheck`)195- `serverExternalPackages`: `dockerode`, `isomorphic-dompurify`, `jsdom`