Self hosted platform for tracking movies, tv shows, video games, books and audiobooks, highly inspired by flox.
This repository is maintained at Guisardo/MediaTrackerPlus.
Fork lineage:
- dnlwttnbrg/MediaTrackerPlus
- Original upstream: bonukai/MediaTracker
This fork intentionally drifts from both upstream repositories to ship Guisardo-specific features, fixes, Docker images, and release cadence. Runtime and database compatibility with either upstream should be treated as best-effort.
| Area | What this fork adds |
|---|---|
| upnext recommendations | Rating an item can asynchronously fetch similar content, add matches to the watchlist, store estimatedRating, and power the Recommended sort. |
| Related content | Details pages resolve provider-similar items against the local library, dedupe them, and age-filter them before rendering. |
| Age gates and parental metadata | Content ratings, descriptors, parental guidance, dateOfBirth-based eligibility, and stable AGE_RESTRICTED responses are applied across browse, details, calendar, statistics, lists, notifications, and related content. |
| Localized metadata and trailers | The client sends Accept-Language; the API resolves configured metadata languages, overlays translated metadata from translation tables, backfills missing translations, and requests localized trailers. |
| Facets and deeplinks | URL-owned filters support genres, years, ratings, languages, creators, publishers, media type, and status; detail metadata pills deep-link back to filtered list views. |
| Group recommendations | Groups and memberships can scope Platform Recommended sorting through cached groupPlatformRating values and group-aware watched exclusions. |
| Full metadata sync | npm run metadata:sync:full runs a one-off forced refresh for every provider-backed item using the server metadata update pipeline. |
| UI and frontend stack | The frontend moved to Vite, React 19, Tailwind CSS 4, Lingui 5, and shadcn/ui patterns with dark-mode and responsive layout improvements. |
| Deployment and CI hardening | Guisardo-owned Docker images, runtime hardening, container security checks, updated GitHub Actions, stricter typing, and broader tests support this fork's release path. |
sequenceDiagram
actor User
participant Client
participant Rating as RatingController
participant Repos as Rating/Seen/List repositories
participant Jobs as setImmediate jobs
participant Service as RecommendationService
participant Providers as Metadata providers
participant Writer as WatchlistWriter
participant GroupCache as Group rating cache
User->>Client: Rate item
Client->>Rating: PUT /api/rating
Rating->>Repos: Upsert rating
Rating->>Repos: Auto-mark as seen when rating exists
Rating-->>Client: 200 OK
Rating->>Jobs: Queue post-response work
Jobs->>Repos: Recalculate mediaItem.platformRating
Jobs->>GroupCache: Recalculate groupPlatformRating
Jobs->>Service: processRating when enabled
Service->>Providers: similar(mediaItem)
Providers-->>Service: Similar items
Service->>Writer: Write watchlist items with estimatedRating
Writer->>Repos: Insert or update listItem
Writer->>Repos: Recalculate mediaItem.platformRating
Writer->>GroupCache: Recalculate groupPlatformRating
flowchart TD
A["GET /api/details/:id + Accept-Language"] --> B["MediaItemController"]
B --> C{"needsDetails?"}
C -- "yes" --> D["updateMetadata refreshes base metadata"]
C -- "no" --> E["continue"]
D --> E
E --> F["Load user dateOfBirth and compute viewer age"]
F --> G{"Age eligible?"}
G -- "no" --> H["403 AGE_RESTRICTED"]
G -- "yes" --> I["Resolve metadata language candidates"]
I --> J["Details query"]
K[("mediaItem / season / episode tables")] --> J
L[("translation tables")] --> J
J --> M["Overlay first available translation and metadataLanguage"]
M --> N["Fetch trailers with primary language"]
M --> O["Resolve related content"]
O --> P["Dedupe and age-filter related items"]
N --> Q["Details payload"]
P --> Q
Q --> R["Details page, locale badge, trailers, related content"]
flowchart LR
A["URL params: facets + orderBy + groupId"] --> B["useFacets + GroupSelector"]
B --> C["GET /api/items/paginated"]
B --> D["GET /api/items/facets"]
C --> E["Validate group, viewer age, and locale preferences"]
D --> F["Validate group and viewer age"]
E --> G["getItemsKnex"]
F --> H["getFacetsKnex"]
G --> I{"platformRecommended + groupId?"}
I -- "yes" --> J["groupPlatformRating join + group watched exclusion"]
I -- "no" --> K["global platformRating or normal library scope"]
J --> L["shared filters + age gating + translation overlay"]
K --> L
H --> M["shared filters + age gating + facet counts"]
L --> N["items + total + ageGatingActive"]
M --> O["facet data"]
N --> P["PaginatedGridItems"]
O --> P
P --> Q["group selector, active chips, age-aware empty state"]
https://guisardo.github.io/MediaTrackerPlus/
git clone https://github.com/Guisardo/MediaTrackerPlus.git
cd MediaTrackerPlus
npm install
npm run build
npm run startTo run a one-off full metadata refresh for every provider-backed item in your configured database:
npm run metadata:sync:fullThis root command loads the repo root .env, aborts if .env is missing, and
requires installed server dependencies. For lower-level usage where you manage
the environment yourself, run npm run metadata:sync:full --prefix server.
npm install
npm run devThis starts the Vite client on http://localhost:7000 with a 0.0.0.0 bind address, and the API server on port 7481 bound to 0.0.0.0.
Changes under client/ refresh in the browser automatically, and changes under server/ rebuild and restart the local API without manually stopping the process.
| Tag | Description |
|---|---|
| latest | stable releases |
docker volume create assets
docker run \
-d \
--name mediatracker-plus \
-p 7481:7481 \
-v /home/YOUR_HOME_DIRECTORY/.config/mediatracker/data:/storage \
-v assets:/assets \
-e TMDB_LANG=en \
-e AUDIBLE_LANG=us \
-e TZ=Europe/London \
guisardo/mediatracker-plus:latestversion: "3"
services:
mediatracker:
container_name: mediatracker-plus
ports:
- 7481:7481
volumes:
- /home/YOUR_HOME_DIRECTORY/.config/mediatracker/data:/storage
- assetsVolume:/assets
environment:
SERVER_LANG: en
TMDB_LANG: en
AUDIBLE_LANG: us
TZ: Europe/London
image: guisardo/mediatracker-plus:latest
volumes:
assetsVolume: null| Parameter | Function |
|---|---|
| -p 7481 | Port web API |
| -v /storage | Directory with database |
| -v /assets | Posters directory |
| -v /logs | Logs directory |
| Name | Description |
|---|---|
| TMDB_LANG | ISO 639-1 country code, one of: om, ab, aa, af, sq, am, ar, hy, as, ay, az, ba, eu, bn, dz, bh, bi, br, bg, my, be, km, ca, zh, co, hr, cs, da, nl, en, eo, et, fo, fj, fi, fr, fy, gl, ka, de, el, kl, gn, gu, ha, he, hi, hu, is, id, ia, ie, ik, iu, ga, it, ja, jw, kn, ks, kk, rw, ky, rn, ko, ku, lo, la, lv, ln, lt, mk, mg, ms, ml, mt, mi, mr, mo, mn, na, ne, no, oc, or, ps, fa, pl, pt, pa, qu, rm, ro, ru, sm, sg, sa, gd, sr, sh, st, tn, sn, sd, si, ss, sk, sl, so, es, su, sw, sv, tl, tg, ta, tt, te, th, bo, ti, to, ts, tr, tk, tw, ug, uk, ur, uz, vi, vo, cy, wo, xh, yi, yo, za, zu |
| AUDIBLE_LANG | ISO 639-1 country code, one of: au, ca, de, es, fr, in, it, jp, gb, us |
| SERVER_LANG | ISO 639-1 country code, one of: da, de, en, es, fr, ko, pt |
| DATABASE_CLIENT | Database client: better-sqlite3 or pg |
| DATABASE_PATH | Only for sqlite, path to database |
| DATABASE_URL | Connection string |
| DATABASE_HOST | Database host |
| DATABASE_PORT | Database port |
| DATABASE_USER | Database user |
| DATABASE_PASSWORD | Database password |
| DATABASE_DATABASE | Database name |
| IGDB_CLIENT_ID | IGDB API key, needed for game lookup |
| IGDB_CLIENT_SECRET | IGDB secret |
| PUID | UserID |
| PGID | GroupID |
| TZ | Timezone, for example Europe/London, see full list |
| ASSETS_PATH | Directory for posters and backdrops, defaults to '$HOME/.mediatracker/img' |
| LOGS_PATH | Directory for logs, defaults to '$HOME/.mediatracker/logs' |
| HOSTNAME | IP address that the server will listen on |
| PORT | Port that the server will listen on |
Every time a user submits a rating, MediaTrackerPlus automatically fetches similar content and adds it to the user's watchlist. The process runs asynchronously after the HTTP response is sent, so rating saves are never delayed.
- Movies and TV shows — similar items are fetched from the TMDB
/similarendpoint. No additional credentials are required beyond the embedded TMDB API key. - Games — similar games are fetched from IGDB using the two-step
similar_gamesquery. TheIGDB_CLIENT_IDandIGDB_CLIENT_SECRETcredentials (configured in Settings → Configuration in the UI) must be set for game recommendations to work. - Books — related books are retrieved from the OpenLibrary subjects API. No credentials are required; OpenLibrary is a public API.
- Audiobooks — not supported by the recommendation engine in the current version.
A new Recommended sort option is available in the watchlist view. Items are scored using the formula:
- When both an
estimatedRating(derived from the trigger rating) and a TMDB community rating are available:score = (estimatedRating × 0.6) + (tmdbRating × 0.4) - When only
estimatedRatingis available (games, books, or items without a TMDB rating):score = estimatedRating - Items with no
estimatedRatingsort to the bottom.
This feature adds an estimatedRating column to the listItem table. The migration runs automatically on first startup and is idempotent — running it more than once is safe.
Migration file: server/src/migrations/20990101000000_listItemEstimatedRating.ts
| Feature | Credential | Where to configure |
|---|---|---|
| Movie and TV recommendations | None (uses embedded TMDB key) | — |
| Game recommendations | IGDB_CLIENT_ID and IGDB_CLIENT_SECRET |
Settings → Configuration in the UI |
| Book recommendations | None (OpenLibrary is public) | — |
Note: IGDB credentials are also required for game metadata search. If games already appear when you search, game recommendations will work automatically. Credentials can be obtained at https://api-docs.igdb.com/#account-creation.
docker build --tag mediatracker-plus:latest https://github.com/Guisardo/MediaTrackerPlus.git
docker run -p 7481:7481 mediatracker- notifications
- calendar
- multiple users
- REST API
- watchlist
- upnext recommendations (automatic watchlist population after every rating)
- docker image
- import from Trakt
- import from goodreads
| Service | Imported data |
|---|---|
| Trakt | Watchlist, watched history, ratings |
| goodreads | Read, Currently Reading, Want to Read, ratings |
| Provider | Media type | Localization |
|---|---|---|
| TMDB | movie, tv show | ✓ |
| IGDB* | video game | ✗ |
| Audible API | audiobooks | ✓ |
| Open Library | books | ✗ |
* IGDB has a limit of 4 requests per second. Because of that IGDB API key is not provided with MediaTracker, it can be acquired here and set in http://localhost:7481/#/settings/configuration
- Jellyfin - Plugin, minimum MediaTracker version:
0.1.0 - Plex - Generate Application token in your MediaTracker instance, and add a webhook in plex
[your MediaTracker url]/api/plex?token=[MediaTracker Application Token] - Kodi - Plugin, minimum MediaTracker version:
0.1.0
- URBANsUNITED (German translation)