IDisposable/jellyfin-plugin-mindthegaps

Jellyfin plugin that finds what's missing and related across your library and builds a todo list to fill the gaps: collections, series, cast & crew filmographies, and recommendations. Adds where-to-watch links and can optionally show missing entries as placeholders.

27

stars

285

commits

C#

primary language

Sep 8, 2026

updated

README

Mind the Gaps Jellyfin Plugin

Mind the Gaps

Finds what's missing and what's related across your Jellyfin library and builds an easy todo list for filling the gaps: movies absent from a collection, episodes absent from a series, films your cast and crew made that you don't own, and related titles worth adding.

Build License Jellyfin 10.11

What it does

A scheduled task scans your library; a dashboard page (Dashboard > Gaps Report) shows the results: filterable by tab and media type, searchable, with links out (TMDB, IMDb, and more) and an on-demand "Where to watch" for each item. What it scans for is on its own page (Dashboard > Mind the Gaps), which is also where the plugin's Settings button lands.

The Mind the Gaps report: movie collections laid out in columns with their missing parts

More screenshots throughout the report guide and the configuration reference.

Every gap is one of three kinds, surfaced as the report's three tabs:

TabWhat it findsExamples
Set completiona missing piece of something you partly owna movie missing from a collection or franchise; a missing season or episode; a music artist's missing albums
Creator worksother work by a person or artist you owna film or series an owned actor or director made; a music artist's wider catalog; an author's other books
Recommendations (the Discover tab)related titles worth exploring and adding (off by default)TMDB "similar" titles for what you own, plus the unowned titles on a TMDB, MDBList, Trakt, or IMDb list and on your JustWatch watchlist, each list shown as its own group

Movies and shows work out of the box; music and books are on by default too. Discogs, Trakt, TheTVDB, MDBList, and JustWatch are opt-in cross-checks and sources that need their own credentials. IMDb lists are opt-in but need no credential.

How the pieces connect: the providers and lists you enable feed three gap patterns, and each pattern surfaces as one report tab, labelled for the media type you are viewing.

flowchart LR
    classDef movies fill:#e3f2fd,stroke:#1565c0,color:#0d47a1;
    classDef shows fill:#ede7f6,stroke:#5e35b1,color:#311b92;
    classDef music fill:#e8f5e9,stroke:#2e7d32,color:#1b5e20;
    classDef books fill:#fff3e0,stroke:#ef6c00,color:#e65100;

    col["TMDB collections and BoxSets"]:::movies --> SET
    cur["Curated studios and keywords"]:::movies --> SET
    ser["Series content (Jellyfin, TheMovieDb, TVmaze, TheTVDB)"]:::shows --> SET
    mbd["MusicBrainz discography, Discogs labels"]:::music --> SET

    ppl["TMDB people"]:::movies --> CRE
    trk["Trakt filmography"]:::movies --> CRE
    ipl["IMDb people lists"]:::movies --> CRE
    art["MusicBrainz and Discogs artists"]:::music --> CRE
    aut["OpenLibrary authors"]:::books --> CRE

    sim["TMDB similar titles"] --> DIS
    tli["TMDB lists"] --> DIS
    mli["MDBList lists"] --> DIS
    ili["IMDb watchlists and lists"] --> DIS
    jwl["JustWatch watchlist"] --> DIS
    mwl["MDBList watchlist"] --> DIS
    twl["Trakt watchlist"] --> DIS
    tmw["TMDB watchlist and favorites"] --> DIS
    tvf["TheTVDB favorites"]:::shows --> DIS
    dwl["Discogs wantlist"]:::music --> DIS
    owl["OpenLibrary want to read"]:::books --> DIS

    SET["Set completion<br/>Movies: Set completion<br/>Shows: Series completion<br/>Music: Discography"]
    CRE["Creator works<br/>Movies and Shows: Creator works<br/>Music: Artist works<br/>Books: Author works"]
    DIS["Discover<br/>related titles and curated lists"]

Series content is the one source that consults several providers at once, so it gets a closer look. For each owned series the plugin gathers every reachable provider's episode list, merges them season by season in your library's own provider order, and reconciles what is left against the episodes you already hold:

flowchart TD
    classDef provider fill:#ede7f6,stroke:#5e35b1,color:#311b92;
    classDef merge fill:#e3f2fd,stroke:#1565c0,color:#0d47a1;
    classDef lib fill:#e8f5e9,stroke:#2e7d32,color:#1b5e20;
    classDef out fill:#fff3e0,stroke:#ef6c00,color:#e65100;

    S["Owned series"]:::lib --> Q{"Any provider reachable? library lists it as a<br/>fetcher, has credentials, and the series carries its id"}
    Q -->|no| B["Surface missing episodes from the library's<br/>own virtual episodes alone"]:::lib
    Q -->|yes| A["Ask each reachable provider for its episode list<br/>(TheMovieDb, TheTVDB, TVmaze)"]:::provider
    A --> R["Rank by the library's fetcher order; TVmaze ranks last,<br/>and no configured order means every credentialed provider"]:::provider
    R --> M["Merge season by season"]:::merge
    M --> M1["The top-ranked provider owns each season it lists"]:::merge
    M --> M2["A lower-ranked provider may add a season none above it<br/>lists, but never contradict a covered one"]:::merge
    M1 --> L["Append the library's own virtual episodes last-chance,<br/>dropping a stale season the authority contradicts"]:::lib
    M2 --> L
    L --> D["Reconcile the merged list against owned episodes<br/>by air date and folded title, not just number"]:::merge
    D --> O1["Owned, even if renumbered, reordered,<br/>or a merged two-parter"]:::out
    D --> O2["Missing, reported as a gap"]:::out
    B --> O2

Features

  • Collection gaps: missing movies in a partially-owned TMDB collection or BoxSet. Movie-franchise only by design (TMDB collections don't model shows).
  • Filmography gaps (TMDB): an owned actor or director's films and series that aren't in your library; films land in the Movies domain and series in the Shows domain, both on the Creator works tab. A big cast and crew is covered a bit at a time across repeated scans, so the list builds up rather than arriving all at once. A relevance filter (a minimum-votes threshold, plus an optional cast-billing limit) drops obscure and bit-part credits so the list stays useful on a large library.
  • Filmography gaps (Trakt): an independent cross-check; opt-in (needs a Trakt client id).
  • Filmography gaps (an IMDb people list): the one creator source not seeded from your library. IMDb types its lists, so a list of people is read as a seed: every unowned film and series each named person made lands on Creator works, which is how you follow a director you own nothing by. Opt-in, keyless, capped per scan.
  • Series content gaps: surfaces the missing episodes Jellyfin already tracks, and (opt-in) cross-checks each owned series against TheMovieDb, TVmaze, and TheTVDB to catch episodes the series' configured metadata provider doesn't list. TheMovieDb and TVmaze are keyless; TheTVDB needs your own v4 API key. The cross-check reconciles by air date and title, not just episode number, so a season a provider numbers differently from your library (a renumber, a reorder, or a two-part episode you keep as one file) is recognized as owned instead of read as missing. Each provider runs when your library lists it as a Shows fetcher and the series carries its id, and when more than one applies they merge in your library's provider order, so your top provider (TheMovieDb for most) owns each series' episode list rather than one that numbers it differently. A refresh icon on each series and season re-checks just that show on the spot, so you can confirm a metadata fix without a full rescan.
  • Curated sets (studio / keyword / label / book subject): complete the movies of a studio ("every A24 film", "every Studio Ghibli film") or a TMDB keyword, beyond what a formal BoxSet covers, a record label's releases via Discogs, or the books tagged with an OpenLibrary subject. Opt-in: studios, keywords, and labels use a type-ahead chip picker (search, pick a match, it becomes a removable chip, no id-hunting); OpenLibrary subjects are entered as slugs (for example science_fiction).
  • Music and books (on by default): complete an album artist's discography and discover a track-only artist's wider catalog (MusicBrainz, with the opt-in Discogs source covering an owned artist that carries a Discogs id), and surface other books in an owned author's bibliography (OpenLibrary). Discogs needs a personal access token.
  • Recommendations: TMDB "similar" titles for what you own; opt-in. Each result lists every owned title that recommends it, not just the first; a TMDB vote floor trims the obscure long tail. The Discover tab groups each suggestion under the owned title that surfaced it.
  • Discovery lists (TMDB, MDBList, Trakt, IMDb, and JustWatch): point the report at a list and complete it the way you complete a collection. Add a TMDB list (its own Scan TMDB lists toggle, with the list ids or themoviedb.org/list URLs pasted in), an MDBList community list (a type-ahead chip picker: search, pick a match, it becomes a removable chip), a Trakt list (its Scan Trakt lists toggle, with each list's numeric id or slug entered; needs a Trakt client id), an IMDb watchlist or list (its Scan IMDb lists toggle, with each ur... user id or ls... list id entered; needs no key, but the list has to be public), or your JustWatch watchlist (its Scan JustWatch toggle plus your session token, since JustWatch publishes no account API); the titles on it you do not own surface in the Discover tab, grouped under the list's name (every one of these but TMDB can include shows as well as movies). A title that is both on a curated list and recommended groups under the list, with the recommendation kept as a secondary source, so the list stays its own group. Opt-in, and MDBList needs a free API key.
  • Your own want-lists: the strongest signal of what to acquire is the list you already keep, so the plugin reads them where the service allows it: your TMDB watchlist and favorites (connected with a two-step button that needs no callback, so your server stays private), an IMDb watchlist or list, a Trakt watchlist, your JustWatch watchlist, your MDBList watchlist, a Discogs wantlist (Music), an OpenLibrary "Want to Read" shelf (Books), and your TheTVDB favorites (Shows). Each is opt-in and its own group on the Discover tab. IMDb and OpenLibrary need no key at all, just a public list; the rest reuse the credential their service already has.
  • Where to watch: streaming availability per item (TMDB watch/providers, officially licensed), looked up on demand or via a background "Look up where to watch" pass; never during the scan. For a missing episode it shows where to watch the show.
  • A usable report: grouped by movies/shows and source, with an A-Z jump bar for the creator-works and recommendation tabs and a coverage badge ("6 of 9 owned, 67%") on collection groups. Each tab loads on demand, and Set completion lays its collapsed series and collections out in responsive columns so a big library is not one very tall list. Filter by type, specials, upcoming, streamable, or dismissed; search (matches the creator/source too); save named view presets or copy a shareable link to the exact view; export the current view to Markdown. Links to TMDB/IMDb/TheTVDB/JustWatch (extended by any external-link provider the host has, including the JustWatch plugin), an "open in Jellyfin" jump to items you already hold, and a search icon that opens a scoped Jellyfin search for any title, series, collection, or creator.
  • Diagnose why something is "missing": a per-gap popup explains the verdict, laying the gap beside the owned items that look like it (owned under the wrong id, an owned item already holds this id, a same-named reboot like V 1984 versus V 2009, or genuinely missing). A "Deeper analysis" confirms against the source provider, an "Export for AI analysis" button downloads the diagnosis as a Markdown dossier (the missing item, the matching rules, the plugin verdict, the owned candidates, and an analysis prompt) to hand to any AI, and an identification audit runs the same check across the domain and pattern you are viewing and downloads as Markdown.
  • Clear down what you have filled: one refresh control on every row and every heading (a domain, a set kind, a group, a season) plus a Clear what I have button for the whole visible list. It checks your library right now and drops the rows you already hold: local, instant, no provider contacted, every domain and kind. A title you have acquired leaves every tab it was on at once, so one film clears out of its collection, the studio set that wanted it, its director's filmography, and any list that suggested it. Anything still missing then prompts to re-check the sources it belongs to against their providers, which also picks up what has been added since the last scan.
  • Batch and whole-set dismissals: resolve or mark "not interested" every episode under a series or season at once, or dismiss a whole creator or recommendation source so it stops being scanned.
  • Dismiss a gap: mark it resolved (not really missing, for example two listed episodes that are a single combined file), not interested (a real gap you do not want), or snooze until release (an upcoming title, which resurfaces on its own once released). Dismissed gaps drop off the list, recoverable via a "Show dismissed" filter.
  • Send to your acquisition stack (opt-in): hand a missing movie or series off to Radarr, Sonarr, or Jellyseerr/Overseerr with a per-row Send action. Radarr takes a movie, Sonarr takes the owning series (it grabs that series' missing episodes), and Jellyseerr/Overseerr requests either. Configured under an "Acquisition stack" settings section (base URLs, API keys, quality profile, root folder, monitor); a Send button appears only for a target you have filled in.
  • Explore a source on demand: an Explore a source button on the report toolbar opens a modal where you pick a kind (studio, keyword, Discogs label, TMDB list, MDBList list) and a source, run it, and its unowned titles merge into the report without a full rescan and without changing your saved settings. Clear explorations removes them again.
  • Webhook: optionally post a summary to a webhook URL (Discord-compatible, carries the server name) when a scan or the "where to watch" pass finishes.
  • Virtual placeholders (opt-in): mint greyed-out "missing" placeholders in place, the way a missing episode renders inside a series. See below.

Installation

  1. In the dashboard: Plugins > Repositories > +.

  2. Add the repository (any name) with this URL:

    https://raw.githubusercontent.com/IDisposable/jellyfin-plugin-mindthegaps/main/manifest.json
    
  3. Open the Catalog tab, find Mind the Gaps under General, and click Install.

  4. Restart Jellyfin.

New releases show up in the catalog automatically.

Beta channel (optional)

To get pre-release builds before they reach the stable channel, add this repository URL instead of the one above:

https://raw.githubusercontent.com/IDisposable/jellyfin-plugin-mindthegaps/main/manifest-beta.json

The beta channel carries every release (stable and pre-release); the stable channel carries only stable releases. Both publish the same plugin, so Jellyfin always offers the highest version it sees and a stable release supersedes the betas that led up to it. Use one channel or the other, not both.

Manual

Download the .zip from the latest release, extract it into a folder under your server's config/plugins/ directory (e.g. config/plugins/MindTheGaps/), and restart Jellyfin.

Requires a server matching the plugin's targetAbi (currently 10.11.0.0, net9.0).

Usage

Open Dashboard > Mind the Gaps and click Rescan now. For collection gaps, your BoxSets need a TMDB id (from the TMDB box-set provider). The scan also runs on a schedule (editable under Dashboard > Scheduled Tasks).

See the report guide for the three pattern tabs (Set completion, Creator works, Recommendations), the filters and saved views, and the per-row actions (where to watch, send, mint, dismiss).

Configuration

In the dashboard, go to Plugins > Mind the Gaps. The source toggles are grouped into Complete what you own (collections, studios, keywords, Discogs labels, series, music discography, books, and an owned actor or director's filmography) and Discover new titles (recommendations, TMDB lists, MDBList lists, Trakt lists, IMDb watchlists, and your JustWatch watchlist). For every setting, what it does, and what changes when you set or clear it, see the configuration reference. In brief, alongside those toggles:

SettingDescription
Metadata country / languageLocale for TMDB lookups and availability.
Max related per itemCaps how many "similar" titles each owned item contributes.
Max creators scanned per runCaps the filmography scan; people are scanned stalest-first, so coverage accumulates over runs and a higher cap covers a large cast and crew faster.
Relevance floorsMinimum TMDB votes for filmography and recommendation gaps (plus an optional cast-billing limit), so Creator works and Recommendations stay actionable on a large library.
Track curated setsGates the studio and keyword sets (the ids below feed it).
Curated studio / keyword idsTMDB company and keyword ids to complete, picked with a type-ahead chip picker.
Scan TMDB listsGates the TMDB discovery lists (the list ids beside it feed it).
AvailabilityTurns "Where to watch" on or off (the per-item lookups and the background pass).
Acquisition stackOptional Radarr / Sonarr / Jellyseerr/Overseerr base URLs, keys, and add settings; enables the per-row Send action.
Webhook URLOptional; posted to (Discord-compatible) when a scan or the "where to watch" pass finishes.
Detailed API loggingOff by default; turn it on to log every external API request and response (the sources, the acquisition sends, TMDB, and the webhook) to the server log while debugging a misbehaving target, then turn it back off.
Trakt client idEnables the opt-in Trakt filmography cross-check.
TheTVDB API keyYour own v4 key; enables the TheTVDB series-content cross-check.
Discogs tokenEnables the opt-in Discogs label and artist source.
TMDB API keyOptional; falls back to the built-in public key.
MDBList API keyOptional (free); enables MDBList community lists as a discovery source.
Scan IMDb listsGates the IMDb discovery lists (the ur... watchlist and ls... list ids beside it feed it). Needs no key, but IMDb serves only what the account has made public.
Follow IMDb people listsReads an IMDb people list from the same ids as a filmography seed, so a director or actor you own nothing by still lands on Creator works. Resolves 50 people per run, stalest first, so a long list is covered over several scans.
Scan TMDB watchlistYour TMDB watchlist, and optionally your favorites. Needs your own TMDB API key plus a connected account (a two-step button; no callback, so the server needs no public address).
Scan Trakt watchlistA Trakt user's watchlist. Needs the Trakt client id and a username; the profile has to be public.
Scan MDBList watchlistYour own MDBList watchlist (not a community list). The API key identifies the account, so there is nothing else to enter.
Scan Discogs wantlistA Discogs wantlist as Music gaps. Needs the Discogs token and a username.
Scan OpenLibrary want to readAn OpenLibrary "Want to Read" shelf as Books gaps. Needs a username and nothing else; the shelf has to be public.
Scan TheTVDB favoritesYour favorited series. Needs the TheTVDB key and the subscriber PIN. Expect few results: a favorite is usually a show you already hold.
TheTVDB subscriber PINOptional. Only needed to read your TheTVDB account (the favorites); the episode cross-check works without it.
JustWatch tokenEnables your own JustWatch watchlist (and, optionally, your likes) as a discovery source. JustWatch issues no api keys, so this is the bearer token from a signed-in browser session, and it expires.

Virtual placeholders (opt-in)

Off by default, the plugin can mint pathless "virtual" placeholder items so a gap renders greyed-out in place. It is a stand-in for proper server support: the server does not reconcile or garbage-collect these, so the plugin does it itself, and everything minted is tagged and fully reversible.

Minting is driven from the report, one gap at a time: each movie row has a Mint button, and you can checkbox several rows and Mint selected. Both run in the background with progress. A collection gap mints into its BoxSet; anything else mints into a catch-all "Mind the Gaps (minted)" collection, and a filmography gap also attaches the person so it shows on that person's page. Every minted item queues a metadata refresh, and at the end of every scan a reconcile pass drops any minted movie the library now owns for real. The settings page keeps only Remove minted movies (with a dry-run preview) to undo everything at once. Missing episodes are not minted here: the server already synthesizes those.

Works alongside your other plugins

Mind the Gaps is self-contained, so it does not depend on any other plugin and will not clash with them. Its links are still extensible without any setup: it folds in whatever your server's own link providers emit, so TMDB and IMDb links come from core, and a JustWatch link lights up automatically if the separate Jellyfin.Plugin.JustWatch is installed. For the architecture behind this, see CONTRIBUTING.

Advanced CSS customization

Every outbound link in the report (the per-row provider links, the links on a creator or set group header, and the ids in the Diagnose popup) is tagged so a stylesheet can target a specific service, for example to inject a service icon. Each link carries:

  • a per-provider class, cgProvider-<service>, with the service name lowercased and stripped to letters and digits: cgProvider-tmdb, cgProvider-imdb, cgProvider-thetvdb, cgProvider-tvmaze, cgProvider-trakt, cgProvider-musicbrainz, cgProvider-discogs, cgProvider-openlibrary, cgProvider-justwatch;
  • a data-provider attribute with the original service name (data-provider="TheTVDB"), for attribute selectors.

Drop CSS into your server via the community Custom CSS or File Transformation plugins (this plugin ships no CSS or icons itself).

Here is a ready-to-paste snippet using Simple Icons (CC0), pulled from their CDN at display time and masked to each link's text color, so the icons match your theme and recolor on hover. Simple Icons carries TMDB, IMDb, Trakt, MusicBrainz, and Discogs; it does not carry TheTVDB, TVmaze, OpenLibrary, or JustWatch, so those are left for you to point at your own hosted SVG.

/* Mind the Gaps: provider icons. Paste into your custom CSS. */
.cgProvider-tmdb::before,
.cgProvider-imdb::before,
.cgProvider-trakt::before,
.cgProvider-musicbrainz::before,
.cgProvider-discogs::before {
  content: "";
  display: inline-block;
  width: 1em;
  height: 1em;
  margin-right: .35em;
  vertical-align: text-bottom;
  background-color: currentColor;
  -webkit-mask: var(--mtg-icon) center / contain no-repeat;
          mask: var(--mtg-icon) center / contain no-repeat;
}
.cgProvider-tmdb        { --mtg-icon: url("https://cdn.simpleicons.org/themoviedatabase"); }
.cgProvider-imdb        { --mtg-icon: url("https://cdn.simpleicons.org/imdb"); }
.cgProvider-trakt       { --mtg-icon: url("https://cdn.simpleicons.org/trakt"); }
.cgProvider-musicbrainz { --mtg-icon: url("https://cdn.simpleicons.org/musicbrainz"); }
.cgProvider-discogs     { --mtg-icon: url("https://cdn.simpleicons.org/discogs"); }

/* Not in Simple Icons. To add one, list its ::before in the rule above and host your own SVG, e.g.:
.cgProvider-thetvdb { --mtg-icon: url("/path/to/thetvdb.svg"); } */

The icons load from the Simple Icons CDN; self-host the SVGs instead if you prefer no external request. The logos are trademarks of their respective services, so this is your choice to display them. These class and attribute names are a stable contract; the link text and layout around them are not.

To go icon-only (hide the text label, show just the icon), add this alongside the snippet above:

.cgProvider-tmdb,
.cgProvider-imdb,
.cgProvider-trakt,
.cgProvider-musicbrainz,
.cgProvider-discogs {
  font-size: 0;          /* collapses the text label */
}
.cgProvider-tmdb::before,
.cgProvider-imdb::before,
.cgProvider-trakt::before,
.cgProvider-musicbrainz::before,
.cgProvider-discogs::before {
  font-size: 1rem;       /* the icon is sized in em, so give it a real size again */
  margin-right: 0;
  vertical-align: middle;
}

Each link carries a title and an aria-label (for example "Open on TheTVDB"), so it stays labeled for tooltips and screen readers even when shown icon-only.

Documentation

  • Configuration reference - every setting, what it does, and what changes when you set or clear it.
  • Report guide - the three tabs, the filters and saved views, and the per-row actions.
  • Roadmap and status - what is built, what is planned, and what is deliberately not.

Contributing

Bug reports, ideas, and pull requests are welcome. See CONTRIBUTING for how to build, test, release, and find your way around the code.

License

MIT. See LICENSE.

Contributors

IDisposable

237 commits

IDisposable/jellyfin-plugin-mindthegaps

Jellyfin plugin that finds what's missing and related across your library and builds a todo list to fill the gaps: collections, series, cast & crew filmographies, and recommendations. Adds where-to-watch links and can optionally show missing entries as placeholders.

27

stars

285

commits

C#

primary language

Sep 8, 2026

updated

README

Mind the Gaps Jellyfin Plugin

Mind the Gaps

Finds what's missing and what's related across your Jellyfin library and builds an easy todo list for filling the gaps: movies absent from a collection, episodes absent from a series, films your cast and crew made that you don't own, and related titles worth adding.

Build License Jellyfin 10.11

What it does

A scheduled task scans your library; a dashboard page (Dashboard > Gaps Report) shows the results: filterable by tab and media type, searchable, with links out (TMDB, IMDb, and more) and an on-demand "Where to watch" for each item. What it scans for is on its own page (Dashboard > Mind the Gaps), which is also where the plugin's Settings button lands.

The Mind the Gaps report: movie collections laid out in columns with their missing parts

More screenshots throughout the report guide and the configuration reference.

Every gap is one of three kinds, surfaced as the report's three tabs:

TabWhat it findsExamples
Set completiona missing piece of something you partly owna movie missing from a collection or franchise; a missing season or episode; a music artist's missing albums
Creator worksother work by a person or artist you owna film or series an owned actor or director made; a music artist's wider catalog; an author's other books
Recommendations (the Discover tab)related titles worth exploring and adding (off by default)TMDB "similar" titles for what you own, plus the unowned titles on a TMDB, MDBList, Trakt, or IMDb list and on your JustWatch watchlist, each list shown as its own group

Movies and shows work out of the box; music and books are on by default too. Discogs, Trakt, TheTVDB, MDBList, and JustWatch are opt-in cross-checks and sources that need their own credentials. IMDb lists are opt-in but need no credential.

How the pieces connect: the providers and lists you enable feed three gap patterns, and each pattern surfaces as one report tab, labelled for the media type you are viewing.

flowchart LR
    classDef movies fill:#e3f2fd,stroke:#1565c0,color:#0d47a1;
    classDef shows fill:#ede7f6,stroke:#5e35b1,color:#311b92;
    classDef music fill:#e8f5e9,stroke:#2e7d32,color:#1b5e20;
    classDef books fill:#fff3e0,stroke:#ef6c00,color:#e65100;

    col["TMDB collections and BoxSets"]:::movies --> SET
    cur["Curated studios and keywords"]:::movies --> SET
    ser["Series content (Jellyfin, TheMovieDb, TVmaze, TheTVDB)"]:::shows --> SET
    mbd["MusicBrainz discography, Discogs labels"]:::music --> SET

    ppl["TMDB people"]:::movies --> CRE
    trk["Trakt filmography"]:::movies --> CRE
    ipl["IMDb people lists"]:::movies --> CRE
    art["MusicBrainz and Discogs artists"]:::music --> CRE
    aut["OpenLibrary authors"]:::books --> CRE

    sim["TMDB similar titles"] --> DIS
    tli["TMDB lists"] --> DIS
    mli["MDBList lists"] --> DIS
    ili["IMDb watchlists and lists"] --> DIS
    jwl["JustWatch watchlist"] --> DIS
    mwl["MDBList watchlist"] --> DIS
    twl["Trakt watchlist"] --> DIS
    tmw["TMDB watchlist and favorites"] --> DIS
    tvf["TheTVDB favorites"]:::shows --> DIS
    dwl["Discogs wantlist"]:::music --> DIS
    owl["OpenLibrary want to read"]:::books --> DIS

    SET["Set completion<br/>Movies: Set completion<br/>Shows: Series completion<br/>Music: Discography"]
    CRE["Creator works<br/>Movies and Shows: Creator works<br/>Music: Artist works<br/>Books: Author works"]
    DIS["Discover<br/>related titles and curated lists"]

Series content is the one source that consults several providers at once, so it gets a closer look. For each owned series the plugin gathers every reachable provider's episode list, merges them season by season in your library's own provider order, and reconciles what is left against the episodes you already hold:

flowchart TD
    classDef provider fill:#ede7f6,stroke:#5e35b1,color:#311b92;
    classDef merge fill:#e3f2fd,stroke:#1565c0,color:#0d47a1;
    classDef lib fill:#e8f5e9,stroke:#2e7d32,color:#1b5e20;
    classDef out fill:#fff3e0,stroke:#ef6c00,color:#e65100;

    S["Owned series"]:::lib --> Q{"Any provider reachable? library lists it as a<br/>fetcher, has credentials, and the series carries its id"}
    Q -->|no| B["Surface missing episodes from the library's<br/>own virtual episodes alone"]:::lib
    Q -->|yes| A["Ask each reachable provider for its episode list<br/>(TheMovieDb, TheTVDB, TVmaze)"]:::provider
    A --> R["Rank by the library's fetcher order; TVmaze ranks last,<br/>and no configured order means every credentialed provider"]:::provider
    R --> M["Merge season by season"]:::merge
    M --> M1["The top-ranked provider owns each season it lists"]:::merge
    M --> M2["A lower-ranked provider may add a season none above it<br/>lists, but never contradict a covered one"]:::merge
    M1 --> L["Append the library's own virtual episodes last-chance,<br/>dropping a stale season the authority contradicts"]:::lib
    M2 --> L
    L --> D["Reconcile the merged list against owned episodes<br/>by air date and folded title, not just number"]:::merge
    D --> O1["Owned, even if renumbered, reordered,<br/>or a merged two-parter"]:::out
    D --> O2["Missing, reported as a gap"]:::out
    B --> O2

Features

  • Collection gaps: missing movies in a partially-owned TMDB collection or BoxSet. Movie-franchise only by design (TMDB collections don't model shows).
  • Filmography gaps (TMDB): an owned actor or director's films and series that aren't in your library; films land in the Movies domain and series in the Shows domain, both on the Creator works tab. A big cast and crew is covered a bit at a time across repeated scans, so the list builds up rather than arriving all at once. A relevance filter (a minimum-votes threshold, plus an optional cast-billing limit) drops obscure and bit-part credits so the list stays useful on a large library.
  • Filmography gaps (Trakt): an independent cross-check; opt-in (needs a Trakt client id).
  • Filmography gaps (an IMDb people list): the one creator source not seeded from your library. IMDb types its lists, so a list of people is read as a seed: every unowned film and series each named person made lands on Creator works, which is how you follow a director you own nothing by. Opt-in, keyless, capped per scan.
  • Series content gaps: surfaces the missing episodes Jellyfin already tracks, and (opt-in) cross-checks each owned series against TheMovieDb, TVmaze, and TheTVDB to catch episodes the series' configured metadata provider doesn't list. TheMovieDb and TVmaze are keyless; TheTVDB needs your own v4 API key. The cross-check reconciles by air date and title, not just episode number, so a season a provider numbers differently from your library (a renumber, a reorder, or a two-part episode you keep as one file) is recognized as owned instead of read as missing. Each provider runs when your library lists it as a Shows fetcher and the series carries its id, and when more than one applies they merge in your library's provider order, so your top provider (TheMovieDb for most) owns each series' episode list rather than one that numbers it differently. A refresh icon on each series and season re-checks just that show on the spot, so you can confirm a metadata fix without a full rescan.
  • Curated sets (studio / keyword / label / book subject): complete the movies of a studio ("every A24 film", "every Studio Ghibli film") or a TMDB keyword, beyond what a formal BoxSet covers, a record label's releases via Discogs, or the books tagged with an OpenLibrary subject. Opt-in: studios, keywords, and labels use a type-ahead chip picker (search, pick a match, it becomes a removable chip, no id-hunting); OpenLibrary subjects are entered as slugs (for example science_fiction).
  • Music and books (on by default): complete an album artist's discography and discover a track-only artist's wider catalog (MusicBrainz, with the opt-in Discogs source covering an owned artist that carries a Discogs id), and surface other books in an owned author's bibliography (OpenLibrary). Discogs needs a personal access token.
  • Recommendations: TMDB "similar" titles for what you own; opt-in. Each result lists every owned title that recommends it, not just the first; a TMDB vote floor trims the obscure long tail. The Discover tab groups each suggestion under the owned title that surfaced it.
  • Discovery lists (TMDB, MDBList, Trakt, IMDb, and JustWatch): point the report at a list and complete it the way you complete a collection. Add a TMDB list (its own Scan TMDB lists toggle, with the list ids or themoviedb.org/list URLs pasted in), an MDBList community list (a type-ahead chip picker: search, pick a match, it becomes a removable chip), a Trakt list (its Scan Trakt lists toggle, with each list's numeric id or slug entered; needs a Trakt client id), an IMDb watchlist or list (its Scan IMDb lists toggle, with each ur... user id or ls... list id entered; needs no key, but the list has to be public), or your JustWatch watchlist (its Scan JustWatch toggle plus your session token, since JustWatch publishes no account API); the titles on it you do not own surface in the Discover tab, grouped under the list's name (every one of these but TMDB can include shows as well as movies). A title that is both on a curated list and recommended groups under the list, with the recommendation kept as a secondary source, so the list stays its own group. Opt-in, and MDBList needs a free API key.
  • Your own want-lists: the strongest signal of what to acquire is the list you already keep, so the plugin reads them where the service allows it: your TMDB watchlist and favorites (connected with a two-step button that needs no callback, so your server stays private), an IMDb watchlist or list, a Trakt watchlist, your JustWatch watchlist, your MDBList watchlist, a Discogs wantlist (Music), an OpenLibrary "Want to Read" shelf (Books), and your TheTVDB favorites (Shows). Each is opt-in and its own group on the Discover tab. IMDb and OpenLibrary need no key at all, just a public list; the rest reuse the credential their service already has.
  • Where to watch: streaming availability per item (TMDB watch/providers, officially licensed), looked up on demand or via a background "Look up where to watch" pass; never during the scan. For a missing episode it shows where to watch the show.
  • A usable report: grouped by movies/shows and source, with an A-Z jump bar for the creator-works and recommendation tabs and a coverage badge ("6 of 9 owned, 67%") on collection groups. Each tab loads on demand, and Set completion lays its collapsed series and collections out in responsive columns so a big library is not one very tall list. Filter by type, specials, upcoming, streamable, or dismissed; search (matches the creator/source too); save named view presets or copy a shareable link to the exact view; export the current view to Markdown. Links to TMDB/IMDb/TheTVDB/JustWatch (extended by any external-link provider the host has, including the JustWatch plugin), an "open in Jellyfin" jump to items you already hold, and a search icon that opens a scoped Jellyfin search for any title, series, collection, or creator.
  • Diagnose why something is "missing": a per-gap popup explains the verdict, laying the gap beside the owned items that look like it (owned under the wrong id, an owned item already holds this id, a same-named reboot like V 1984 versus V 2009, or genuinely missing). A "Deeper analysis" confirms against the source provider, an "Export for AI analysis" button downloads the diagnosis as a Markdown dossier (the missing item, the matching rules, the plugin verdict, the owned candidates, and an analysis prompt) to hand to any AI, and an identification audit runs the same check across the domain and pattern you are viewing and downloads as Markdown.
  • Clear down what you have filled: one refresh control on every row and every heading (a domain, a set kind, a group, a season) plus a Clear what I have button for the whole visible list. It checks your library right now and drops the rows you already hold: local, instant, no provider contacted, every domain and kind. A title you have acquired leaves every tab it was on at once, so one film clears out of its collection, the studio set that wanted it, its director's filmography, and any list that suggested it. Anything still missing then prompts to re-check the sources it belongs to against their providers, which also picks up what has been added since the last scan.
  • Batch and whole-set dismissals: resolve or mark "not interested" every episode under a series or season at once, or dismiss a whole creator or recommendation source so it stops being scanned.
  • Dismiss a gap: mark it resolved (not really missing, for example two listed episodes that are a single combined file), not interested (a real gap you do not want), or snooze until release (an upcoming title, which resurfaces on its own once released). Dismissed gaps drop off the list, recoverable via a "Show dismissed" filter.
  • Send to your acquisition stack (opt-in): hand a missing movie or series off to Radarr, Sonarr, or Jellyseerr/Overseerr with a per-row Send action. Radarr takes a movie, Sonarr takes the owning series (it grabs that series' missing episodes), and Jellyseerr/Overseerr requests either. Configured under an "Acquisition stack" settings section (base URLs, API keys, quality profile, root folder, monitor); a Send button appears only for a target you have filled in.
  • Explore a source on demand: an Explore a source button on the report toolbar opens a modal where you pick a kind (studio, keyword, Discogs label, TMDB list, MDBList list) and a source, run it, and its unowned titles merge into the report without a full rescan and without changing your saved settings. Clear explorations removes them again.
  • Webhook: optionally post a summary to a webhook URL (Discord-compatible, carries the server name) when a scan or the "where to watch" pass finishes.
  • Virtual placeholders (opt-in): mint greyed-out "missing" placeholders in place, the way a missing episode renders inside a series. See below.

Installation

  1. In the dashboard: Plugins > Repositories > +.

  2. Add the repository (any name) with this URL:

    https://raw.githubusercontent.com/IDisposable/jellyfin-plugin-mindthegaps/main/manifest.json
    
  3. Open the Catalog tab, find Mind the Gaps under General, and click Install.

  4. Restart Jellyfin.

New releases show up in the catalog automatically.

Beta channel (optional)

To get pre-release builds before they reach the stable channel, add this repository URL instead of the one above:

https://raw.githubusercontent.com/IDisposable/jellyfin-plugin-mindthegaps/main/manifest-beta.json

The beta channel carries every release (stable and pre-release); the stable channel carries only stable releases. Both publish the same plugin, so Jellyfin always offers the highest version it sees and a stable release supersedes the betas that led up to it. Use one channel or the other, not both.

Manual

Download the .zip from the latest release, extract it into a folder under your server's config/plugins/ directory (e.g. config/plugins/MindTheGaps/), and restart Jellyfin.

Requires a server matching the plugin's targetAbi (currently 10.11.0.0, net9.0).

Usage

Open Dashboard > Mind the Gaps and click Rescan now. For collection gaps, your BoxSets need a TMDB id (from the TMDB box-set provider). The scan also runs on a schedule (editable under Dashboard > Scheduled Tasks).

See the report guide for the three pattern tabs (Set completion, Creator works, Recommendations), the filters and saved views, and the per-row actions (where to watch, send, mint, dismiss).

Configuration

In the dashboard, go to Plugins > Mind the Gaps. The source toggles are grouped into Complete what you own (collections, studios, keywords, Discogs labels, series, music discography, books, and an owned actor or director's filmography) and Discover new titles (recommendations, TMDB lists, MDBList lists, Trakt lists, IMDb watchlists, and your JustWatch watchlist). For every setting, what it does, and what changes when you set or clear it, see the configuration reference. In brief, alongside those toggles:

SettingDescription
Metadata country / languageLocale for TMDB lookups and availability.
Max related per itemCaps how many "similar" titles each owned item contributes.
Max creators scanned per runCaps the filmography scan; people are scanned stalest-first, so coverage accumulates over runs and a higher cap covers a large cast and crew faster.
Relevance floorsMinimum TMDB votes for filmography and recommendation gaps (plus an optional cast-billing limit), so Creator works and Recommendations stay actionable on a large library.
Track curated setsGates the studio and keyword sets (the ids below feed it).
Curated studio / keyword idsTMDB company and keyword ids to complete, picked with a type-ahead chip picker.
Scan TMDB listsGates the TMDB discovery lists (the list ids beside it feed it).
AvailabilityTurns "Where to watch" on or off (the per-item lookups and the background pass).
Acquisition stackOptional Radarr / Sonarr / Jellyseerr/Overseerr base URLs, keys, and add settings; enables the per-row Send action.
Webhook URLOptional; posted to (Discord-compatible) when a scan or the "where to watch" pass finishes.
Detailed API loggingOff by default; turn it on to log every external API request and response (the sources, the acquisition sends, TMDB, and the webhook) to the server log while debugging a misbehaving target, then turn it back off.
Trakt client idEnables the opt-in Trakt filmography cross-check.
TheTVDB API keyYour own v4 key; enables the TheTVDB series-content cross-check.
Discogs tokenEnables the opt-in Discogs label and artist source.
TMDB API keyOptional; falls back to the built-in public key.
MDBList API keyOptional (free); enables MDBList community lists as a discovery source.
Scan IMDb listsGates the IMDb discovery lists (the ur... watchlist and ls... list ids beside it feed it). Needs no key, but IMDb serves only what the account has made public.
Follow IMDb people listsReads an IMDb people list from the same ids as a filmography seed, so a director or actor you own nothing by still lands on Creator works. Resolves 50 people per run, stalest first, so a long list is covered over several scans.
Scan TMDB watchlistYour TMDB watchlist, and optionally your favorites. Needs your own TMDB API key plus a connected account (a two-step button; no callback, so the server needs no public address).
Scan Trakt watchlistA Trakt user's watchlist. Needs the Trakt client id and a username; the profile has to be public.
Scan MDBList watchlistYour own MDBList watchlist (not a community list). The API key identifies the account, so there is nothing else to enter.
Scan Discogs wantlistA Discogs wantlist as Music gaps. Needs the Discogs token and a username.
Scan OpenLibrary want to readAn OpenLibrary "Want to Read" shelf as Books gaps. Needs a username and nothing else; the shelf has to be public.
Scan TheTVDB favoritesYour favorited series. Needs the TheTVDB key and the subscriber PIN. Expect few results: a favorite is usually a show you already hold.
TheTVDB subscriber PINOptional. Only needed to read your TheTVDB account (the favorites); the episode cross-check works without it.
JustWatch tokenEnables your own JustWatch watchlist (and, optionally, your likes) as a discovery source. JustWatch issues no api keys, so this is the bearer token from a signed-in browser session, and it expires.

Virtual placeholders (opt-in)

Off by default, the plugin can mint pathless "virtual" placeholder items so a gap renders greyed-out in place. It is a stand-in for proper server support: the server does not reconcile or garbage-collect these, so the plugin does it itself, and everything minted is tagged and fully reversible.

Minting is driven from the report, one gap at a time: each movie row has a Mint button, and you can checkbox several rows and Mint selected. Both run in the background with progress. A collection gap mints into its BoxSet; anything else mints into a catch-all "Mind the Gaps (minted)" collection, and a filmography gap also attaches the person so it shows on that person's page. Every minted item queues a metadata refresh, and at the end of every scan a reconcile pass drops any minted movie the library now owns for real. The settings page keeps only Remove minted movies (with a dry-run preview) to undo everything at once. Missing episodes are not minted here: the server already synthesizes those.

Works alongside your other plugins

Mind the Gaps is self-contained, so it does not depend on any other plugin and will not clash with them. Its links are still extensible without any setup: it folds in whatever your server's own link providers emit, so TMDB and IMDb links come from core, and a JustWatch link lights up automatically if the separate Jellyfin.Plugin.JustWatch is installed. For the architecture behind this, see CONTRIBUTING.

Advanced CSS customization

Every outbound link in the report (the per-row provider links, the links on a creator or set group header, and the ids in the Diagnose popup) is tagged so a stylesheet can target a specific service, for example to inject a service icon. Each link carries:

  • a per-provider class, cgProvider-<service>, with the service name lowercased and stripped to letters and digits: cgProvider-tmdb, cgProvider-imdb, cgProvider-thetvdb, cgProvider-tvmaze, cgProvider-trakt, cgProvider-musicbrainz, cgProvider-discogs, cgProvider-openlibrary, cgProvider-justwatch;
  • a data-provider attribute with the original service name (data-provider="TheTVDB"), for attribute selectors.

Drop CSS into your server via the community Custom CSS or File Transformation plugins (this plugin ships no CSS or icons itself).

Here is a ready-to-paste snippet using Simple Icons (CC0), pulled from their CDN at display time and masked to each link's text color, so the icons match your theme and recolor on hover. Simple Icons carries TMDB, IMDb, Trakt, MusicBrainz, and Discogs; it does not carry TheTVDB, TVmaze, OpenLibrary, or JustWatch, so those are left for you to point at your own hosted SVG.

/* Mind the Gaps: provider icons. Paste into your custom CSS. */
.cgProvider-tmdb::before,
.cgProvider-imdb::before,
.cgProvider-trakt::before,
.cgProvider-musicbrainz::before,
.cgProvider-discogs::before {
  content: "";
  display: inline-block;
  width: 1em;
  height: 1em;
  margin-right: .35em;
  vertical-align: text-bottom;
  background-color: currentColor;
  -webkit-mask: var(--mtg-icon) center / contain no-repeat;
          mask: var(--mtg-icon) center / contain no-repeat;
}
.cgProvider-tmdb        { --mtg-icon: url("https://cdn.simpleicons.org/themoviedatabase"); }
.cgProvider-imdb        { --mtg-icon: url("https://cdn.simpleicons.org/imdb"); }
.cgProvider-trakt       { --mtg-icon: url("https://cdn.simpleicons.org/trakt"); }
.cgProvider-musicbrainz { --mtg-icon: url("https://cdn.simpleicons.org/musicbrainz"); }
.cgProvider-discogs     { --mtg-icon: url("https://cdn.simpleicons.org/discogs"); }

/* Not in Simple Icons. To add one, list its ::before in the rule above and host your own SVG, e.g.:
.cgProvider-thetvdb { --mtg-icon: url("/path/to/thetvdb.svg"); } */

The icons load from the Simple Icons CDN; self-host the SVGs instead if you prefer no external request. The logos are trademarks of their respective services, so this is your choice to display them. These class and attribute names are a stable contract; the link text and layout around them are not.

To go icon-only (hide the text label, show just the icon), add this alongside the snippet above:

.cgProvider-tmdb,
.cgProvider-imdb,
.cgProvider-trakt,
.cgProvider-musicbrainz,
.cgProvider-discogs {
  font-size: 0;          /* collapses the text label */
}
.cgProvider-tmdb::before,
.cgProvider-imdb::before,
.cgProvider-trakt::before,
.cgProvider-musicbrainz::before,
.cgProvider-discogs::before {
  font-size: 1rem;       /* the icon is sized in em, so give it a real size again */
  margin-right: 0;
  vertical-align: middle;
}

Each link carries a title and an aria-label (for example "Open on TheTVDB"), so it stays labeled for tooltips and screen readers even when shown icon-only.

Documentation

  • Configuration reference - every setting, what it does, and what changes when you set or clear it.
  • Report guide - the three tabs, the filters and saved views, and the per-row actions.
  • Roadmap and status - what is built, what is planned, and what is deliberately not.

Contributing

Bug reports, ideas, and pull requests are welcome. See CONTRIBUTING for how to build, test, release, and find your way around the code.

License

MIT. See LICENSE.

Contributors

IDisposable

237 commits

Languages

C#

80.4%

JavaScript

14.8%

HTML

4.0%