A modern, AI-powered recommendation engine for your Jellyfin media server. Built with React (Vite), Node.js, TypeScript, and SQLite.
- π€ AI-Powered Discovery β Uses Google Gemini or OpenRouter models to analyze your taste and find hidden gems you haven't seen yet.
- β "Trust No AI" Verification β Every suggestion is strictly verified against Jellyseerr/TMDB to ensure valid metadata and functional links.
- π§ Dynamic Taste Profile β The system learns your preferences from your watch history and generates a personalized textual summary of your movie and TV tastes.
- οΏ½ Weekly Watchlist β Pre-generated personalized recommendations every Monday morning using dual-AI system (Curator + Critic agents).
- π Trending Page β Discover what's popular right now with smart filtering that excludes content you've already watched or requested.
- οΏ½π User Statistics Dashboard β Visualize your watching habits with beautiful charts, key metrics, and your detailed AI taste profile.
- π« Smart Filtering β Automatically hides content you already own, have watched, or explicitly blocked.
- π Decade Range Filter β Narrow recommendations by release year with an interactive dual-handle slider (1900β2026).
- π Watchlist Management β Organize your "To Watch" list with filtering (Movies vs. TV) and sorting.
- π Jellyseerr Integration β Request recommended media directly with one click (Optimistic UI).
- πΎ Data Persistence β Uses a local SQLite database (via Prisma) to store your history, ratings, and metadata safely.
- πΌοΈ Local Image Caching β Downloads and serves all poster/backdrop images locally to prevent broken links when Jellyseerr IP changes.
- π± Responsive Design β Mobile-first UI with backdrop images on mobile, poster images on desktop.
- βοΈ UI Configuration β Edit API keys and URLs directly in the browser via the Settings page.
- π Legacy Import β Non-destructive import tool to migrate data from the old v1
database.json. - π§ Production-Ready Rate Limiting β Smart rate limiting supports large imports (1000+ items) with separate limits per operation type.
- π Reverse Proxy Support β Full support for Nginx, ZimaOS, and other reverse proxy environments with proper header forwarding.
- π Session-Based Authentication β Secure server-side sessions with AES-256-GCM encrypted credentials and offline fallback support.
The fastest way to get started. Recommended for most users.
| Requirement | Description |
|---|---|
| Docker & Docker Compose | Install Docker |
| Jellyfin Server | Your media server URL (e.g., http://192.168.1.100:8096) |
| Jellyseerr | For metadata enrichment & requests (e.g., http://192.168.1.100:5055) |
| Google Gemini API Key | Free at Google AI Studio (Default Provider) |
| OpenRouter API Key | Optional OpenRouter |
You can choose between two AI providers:
- Direct access to Gemini models via Google's official SDK.
- Cost: Free tier available (Google AI Studio).
- Configuration: Set
AI_PROVIDER=google(or leave default) andGEMINI_API_KEY.
- Unified access to Gemini models via OpenRouter's API.
- Why use it? If you prefer a centralized API for multiple models or need specific routing.
- Configuration:
- Set
AI_PROVIDER=openrouter - Set
OPENROUTER_API_KEY - Set
AI_MODEL(e.g.,google/gemini-3.1-flash-lite:free) - Note: Prefix is required!
- Set
Docker Compose Example:
environment:
- AI_PROVIDER=openrouter
- OPENROUTER_API_KEY=sk-or-v1-...
- AI_MODEL=google/gemini-3.1-flash-lite:freeservices:
frontend:
image: ghcr.io/jessepesse/jellyfin-ai-recommender-frontend:latest
ports:
- "5173:80"
depends_on:
backend:
condition: service_healthy
backend:
image: ghcr.io/jessepesse/jellyfin-ai-recommender-backend:latest
volumes:
- ./data:/data # SQLite database
- ./images:/app/images # Cached posters/backdrops
environment:
- DATABASE_URL=file:/data/dev.db
- IMAGE_DIR=/app/images
# Optional: Set these here OR configure via Setup Wizard in browser
# - JELLYFIN_URL=http://your-jellyfin:8096
# - JELLYSEERR_URL=http://your-jellyseerr:5055
# - JELLYSEERR_API_KEY=your-api-key
# - AI_PROVIDER=google # 'google' or 'openrouter'
# - GEMINI_API_KEY=your-gemini-key # Required if provider is google
# - OPENROUTER_API_KEY=your-openrouter-key # Required if provider is openrouter
# - AI_MODEL=gemini-3.1-flash-lite # Optional: Custom model
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:3001/api/health"]
interval: 30s
timeout: 10s
retries: 3docker compose up -d- Open http://localhost:5173 in your browser
- The Setup Wizard will guide you through configuration:
- Enter your Jellyfin server URL
- Enter your Jellyseerr URL and API key
- Select AI Provider (Google Gemini or OpenRouter)
- Enter your API credentials
- Log in with your Jellyfin credentials
- Start getting AI-powered recommendations! π
On first startup, the system creates a local admin user for emergency access:
| Setting | Default Value |
|---|---|
| Username | admin |
| Password | Set via INITIAL_ADMIN_PASSWORD env variable (default: admin123) |
Important
Change the admin password immediately after first login! Go to Settings β Admin Account and set a strong password.
The admin user provides:
- Offline Access: Login even when Jellyfin server is unavailable
- Emergency Recovery: Access the system if Jellyfin authentication fails
- Token Expiry: Sessions expire after 30 days for security
docker compose pull && docker compose up -dThis project is a full-stack monorepo split into a separate Frontend and Backend.
- Frontend (
/frontend): React + Vite + Tailwind CSS. Handles the UI, state management, and optimistic updates. - Backend (
/backend): Node.js + Express + TypeScript. Handles API proxies, AI logic, database operations (Prisma), and Jellyseerr integration. - Database: SQLite (
dev.db) for user history and media metadata.
- Node.js (v22+) & npm
- Jellyfin Server (accessible URL)
- Jellyseerr Server (for metadata enrichment & requests)
- AI Provider API Key (Google Gemini OR OpenRouter)
Clone the repo and install dependencies for both services:
git clone https://github.com/jessepesse/jellyfin-ai-recommender.git
cd jellyfin-ai-recommender
# Install root tools (concurrently) and project dependencies
npm install
npm run install:allOption A: Setup Wizard (Recommended) Just start the app! You will be greeted by a Setup Wizard in the browser to enter your URLs and Keys. They will be saved to the local database.
Option B: Environment Variables (Advanced) Copy backend/.env.example to backend/.env and fill in your values:
cp backend/.env.example backend/.env
# Then edit backend/.env with your actual valuesInitialize the SQLite database and apply the schema:
cd backend
npm run db:migrate
npm run db:generate # Generate Prisma client typesStart both the Frontend and Backend with a single command from the root directory:
npm run dev- Frontend (UI): http://localhost:5173
- Backend (API): http://localhost:3001
This repo includes a production-ready docker-compose.prod.yml. It sets up the Node.js backend and serves the frontend via Nginx.
# Build and start containers
docker compose -f docker-compose.prod.yml up -d --buildAccess: The app is available at http://localhost:5173 (frontend) and the backend API at http://localhost:3001.
Persisted Data:
- SQLite database:
./data/dev.db - Cached images:
./images/(posters and backdrops)
Initial Setup: After first run, migrate existing images to local cache:
docker-compose -f docker-compose.prod.yml exec backend npm run db:migrate-imagesFor development with Docker and automatic hot-reload, use the development compose file:
# Start development containers with hot reload
docker compose -f docker-compose.development.yml up --build
# Or in detached mode
docker compose -f docker-compose.development.yml up -d --buildFeatures:
- π Hot Reload: Code changes are automatically detected and reloaded
- π Source Mounting:
src/directories are mounted read-only into containers - π§ͺ Isolated Dependencies:
node_modulesstay inside containers - π Development Mode: Full error messages and debugging output
Development Access:
- Frontend (Vite HMR):
http://localhost:5173 - Backend API:
http://localhost:3001
View Logs:
# All services
docker compose -f docker-compose.development.yml logs -f
# Backend only
docker compose -f docker-compose.development.yml logs -f backend-dev
# Frontend only
docker compose -f docker-compose.development.yml logs -f frontend-devStop Development:
docker compose -f docker-compose.development.yml downIf you plan to expose this application to the public internet (e.g., via Cloudflare Tunnel or Nginx Proxy Manager), you must configure the CORS policy to prevent security risks.
Edit your docker-compose.prod.yml and uncomment the CORS_ORIGIN line in the backend service:
environment:
- CORS_ORIGIN=https://ai.yourdomain.comWhy this matters: By default, the app only allows private networks (LAN IPs like 192.168.x.x). Without setting CORS_ORIGIN, public domains will be blocked. Setting this variable allows your specific domain while still blocking malicious sites.
If your Jellyseerr or Jellyfin is hosted on a custom domain (not localhost or standard TMDB domains), you need to allowlist it to bypass SSRF protection:
# In docker-compose.prod.yml, add to backend environment:
environment:
- ALLOWED_IMAGE_DOMAINS=myjellyseerr.example.com,myjellyfin.example.comFormat: Comma-separated list of domains (no http://, no paths)
Default allowed: image.tmdb.org, themoviedb.org, localhost, 127.0.0.1, Docker network hosts
Why this is needed: The app has strict SSRF (Server-Side Request Forgery) protection that only allows requests to trusted domains. If you see errors like [SSRF] Blocked request to non-allowlisted domain, add your domain to this list.
Ensure your reverse proxy (Nginx, Cloudflare, etc.) forwards the correct headers:
X-Real-IPX-Forwarded-ForX-Forwarded-Proto
Recommended Setup: Use separate domains for frontend and backend.
Example:
- Frontend:
https://recommender.yourdomain.com - Backend API:
https://api.yourdomain.com
- β Cleaner architecture - Frontend and backend are independent services
- β Easier scaling - Deploy services on different servers if needed
- β No proxy complexity - Direct API calls, no Vite/Nginx proxy configuration
- β Better debugging - Clear separation of concerns
Option A: Docker Compose (Recommended)
Edit docker-compose.prod.yml:
services:
frontend:
environment:
- VITE_BACKEND_URL=https://api.yourdomain.comOption B: Docker Run
docker run -e VITE_BACKEND_URL=https://api.yourdomain.com \
ghcr.io/jessepesse/jellyfin-ai-recommender-frontend:latestOption C: Build-time (Dockerfile)
docker build \
--build-arg VITE_BACKEND_URL=https://api.yourdomain.com \
-t my-frontend ./frontendOption D: Local Development
Create frontend/.env:
VITE_BACKEND_URL=http://localhost:3001If VITE_BACKEND_URL is not set, the frontend defaults to http://localhost:3001.
-
Create tunnels for both services:
# Backend tunnel cloudflared tunnel route dns jellyfin-ai-backend api.yourdomain.com # Frontend tunnel cloudflared tunnel route dns jellyfin-ai-frontend recommender.yourdomain.com
-
Configure frontend to use API domain:
# docker-compose.prod.yml services: frontend: environment: - VITE_BACKEND_URL=https://api.yourdomain.com
-
Rebuild and restart:
docker-compose -f docker-compose.prod.yml up -d --build
See frontend/nginx.conf for a reference configuration.
π Usage Guide
Login: Use your Jellyfin credentials. The app authenticates against your server.
*Offline Mode:* Uses cached credentials if Jellyfin is unreachable.
*Admin Mode:* Use local `admin` account for emergency access.
Get Recommendations:
Select Movie or TV Series.
Optionally select a Genre, Mood, or Decade Range.
Click "Get Recommendations". The AI will analyze your history and suggest 10 new items.
Actions:
ποΈ Mark as Watched: Adds to your history.
π Add to Watchlist: Saves to your personal list.
π« Block: Removes the item and signals the AI to avoid similar content.
π₯ Request: Sends a download request to Jellyseerr.
Settings:
System Config: Update your API keys/URLs at any time.
Import: Paste your old database.json to migrate legacy data.
Export: Download a backup of your current database.
Run this to download all poster and backdrop images to local cache:
# Development
cd backend
npm run db:migrate-images
# Docker
docker compose -f docker-compose.prod.yml exec backend npm run db:migrate-imagesExport your database to JSON:
# Development
cd backend
npm run db:backup
# Docker
docker compose -f docker-compose.prod.yml exec backend npm run db:backupBackups are saved to ./data/backup_latest.json and timestamped files.
This project implements comprehensive security measures including:
Network & Request Protection:
- 5-layer SSRF (Server-Side Request Forgery) protection
- Input validation with Zod schemas
- Rate limiting on all endpoints (5 different limiters)
- Security headers via Helmet
- Strict CORS policy with private network allowlist
- Local image caching (eliminates external URL dependencies)
Authentication & Authorization:
- Session-based authentication with AES-256-GCM encrypted credentials
- HMAC-SHA256 session token hashing (raw token never stored in DB)
- PBKDF2 password hashing for local admin account
- Middleware-enforced route protection (
authMiddleware,requireAdmin) - Automatic session expiry and purging
Privacy:
- No sensitive data logging (passwords never logged)
- Token invalidation on password change
For security policy and known CodeQL alerts, see SECURITY.md
This project is licensed under the GNU Affero General Public License v3.0 (AGPLv3).
β
Personal & Commercial use allowed
β οΈ Modifications must be open-sourced if distributed/hosted for others.
See LICENSE for details.
Made with β€οΈ for Jellyfin enthusiasts.
