Skip to content

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

30 Commits
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Missing Episodes for Jellyfin

Find the gaps in your TV library — and let Sonarr fill them.

Scans your Jellyfin library, your Sonarr instance, or queries TMDB when neither has the data. Shows exactly what's missing per show, per season, per episode, with thumbnails, air dates, storage sizes, paths, and a one-click Search button that hands off to Sonarr.

Status: stable (v1.0.8). Works end-to-end on Jellyfin 10.11.x + Sonarr v3. Install via the plugin repository URL below or as a manual DLL drop.


📑 Table of contents


✨ Features

  • Three scan sources
    • Sonarr — trust Sonarr's monitored + hasFile flags. Authoritative, fast.
    • Jellyfin / Virtual items — use Jellyfin's own missing-episode items (requires Display missing episodes within seasons in the library).
    • Jellyfin / TMDB — query TMDB for each show's canonical episode list. Works without any Jellyfin library reconfiguration. Needs a free TMDB API key.
    • Jellyfin / Gap detection — find numbering holes in what's on disk. No external deps, limited but useful.
  • Accuracy layers — presence of an episode is derived from three sources, each filling gaps the others miss:
    1. Jellyfin library — non-virtual items the library scan imported.
    2. Filename parser — walks series.Path on disk and parses S01E05 / 1x05 / multi-ep patterns. Catches files Jellyfin never imported (e.g. shows where the library lists 0 episodes despite 144 GB on disk). Falls back to Sonarr's path when Jellyfin's is stale.
    3. Sonarr hasFile (if configured) — the authoritative per-episode flag. Catches episodes stored in folders neither Jellyfin nor Sonarr's canonical path covers (scattered-release layouts). Also drops unmonitored-in-Sonarr episodes from the missing list when Only monitored is on.
  • One-click Sonarr search — per episode, per season, per show, or everything currently visible.
  • Auto-search (optional) — scan + dispatch to Sonarr on a schedule.
  • Rescan this show — targeted refresh from the detail view, no full library scan.
  • Active / Ignored tabs — hide shows you never care about; click an ignored card to open its detail with a Remove from ignore list button.
  • All / TV / Anime filter + title search. Anime detection uses Sonarr's series type or Jellyfin genre/tag.
  • Persistent results + rolling 25-scan history — survives restarts, clearable from the UI.
  • Live progress — background scan with progress strip (X / Y series, current show, percentage). Leave the page, come back, and it picks up. Toast fires when done.
  • Admin sidebar entryMissing Episodes in the Jellyfin admin sidebar, not buried in Plugins.
  • Storage + path — GB chip on each card (computed from on-disk size when Jellyfin can read the folder; from Sonarr otherwise). Series path in the detail view. Smart reconciliation: trust whichever side has files.
  • Sorts — Most missing / Title / Recent / Size / Progress (least-complete first).
  • Admin-only API — every endpoint requires the RequiresElevation policy.

🖼️ Screenshots

The dashboard

The whole plugin at a glance. Four stat cards at the top (shows with gaps, total missing, source, time since last scan), a collapsible scan-history block right below, the filter toolbar, and a poster grid sorted by most-missing by default. Each card has a red missing-count badge in the top-right corner and a dark monospace size chip in the bottom-left — fits a lot of signal into a small space without looking busy.

Dashboard — hero, stats, scan history and poster grid

Poster grid

Closer look at the cards. Badges turn orange when the count is ≤2 so you can see at a glance which shows are "nearly done" vs "wildly behind". Anime detection auto-applies a small pill next to the title (sourced from Sonarr's seriesType or a Jellyfin genre/tag). Hover lift is subtle and hover-shadow softens, so scanning with your eyes doesn't feel like a slot machine.

Poster grid with per-show missing badges and GB chips

Toolbar

Three independent filters (scope, type, text) plus five sorts: Most missing (default), Title, Recent (most recently aired missing episode first), Size (biggest-on-disk first, good for reclaiming space), and Progress (least-complete first, good for binge planning). The Search all button on the right hands every currently-visible missing episode — respecting all three filters — off to Sonarr in one batched EpisodeSearch, with a confirm dialog so you don't accidentally kick off 4,000 searches.

Toolbar — scope, type, search, sort, bulk search

Scan history

A rolling log of the last 25 scans — time, source, mode, missing total, show count, duration. Lets you eyeball trends ("did my missing count drop after last week's Sonarr run?") and surface pathological scans (the 440s outlier here is early-days before I put the single-series optimisation in).

Scan history entries

Show detail

Clicking a poster swaps the whole page for a dedicated detail view — no modal, no popup. Header carries the show's poster, missing count pill, network, status, Have / Total, completion percentage, on-disk size, and series path (not shown in this crop). Three action buttons: Search all missing (batched Sonarr search for just this show), Rescan this show (refreshes only this show, runs in ~1s under the same progress strip the full scan uses), and Ignore this show (hides it from scans, accessible later from the Ignored tab).

Detail header — missing count, network, episodes, completion %, size

Seasons live below the header as an accordion. Each row shows Have / Total, the absolute missing count, and a mini progress bar. Seasons with zero gaps don't appear at all. Per-season Search season button lets you batch-request just one season without touching the others.

Season accordion with Have/Total counts

Pop any season open and you get proper episode rows — 16:9 thumbnail (from Sonarr's screenshot images or TMDB still_path), season/episode code, title, overview, air date, and a per-episode Search button. Queued episodes turn green with a ✓ so you know the click landed. Scroll is contained in the episode list, header stays visible.

Expanded season with episodes and search buttons

Settings

The two segmented controls on the left are the core: Scan source (Sonarr or Jellyfin) and — only when Jellyfin is selected — Jellyfin method (Virtual items / TMDB / Gap detection). Both auto-save on click, no Save button needed. The method explainer changes text based on your picks and adds a note when Sonarr is configured, so there's no silent magic.

Scan source + Jellyfin method + auto-search interval

The rest of the settings row. Auto-send missing episodes to Sonarr turns on the background worker (scans on Auto-search every (hours) interval and dispatches every missing episode automatically). Only monitored, Ignore specials, and Ignore unaired are the three noise-reduction toggles — each does what it says. Test Sonarr and Test TMDB hit each service's health endpoint and surface the real error if it fails, so you find out you typed the URL wrong before your first scan, not after.

Toggles and Save row


⚙️ Installation

Option 1 — Jellyfin plugin repository (recommended)

  1. Go to Dashboard → Plugins → Repositories → ➕
  2. Name: Missing Episodes
  3. URL:
https://raw.githubusercontent.com/ZL154/MissingEpisodesJellyfin/main/manifest.json
  1. SaveCatalog → install Missing Episodes
  2. Restart Jellyfin
  3. Dashboard → Missing Episodes → Settings to configure

Option 2 — Manual install

  1. Download the latest zip from Releases.
  2. Extract the DLL into your Jellyfin plugin directory:
    • Docker: /config/plugins/MissingEpisodes_x.y.z.w/
    • Linux: /var/lib/jellyfin/plugins/MissingEpisodes_x.y.z.w/
    • Windows: %ProgramData%\Jellyfin\Server\plugins\MissingEpisodes_x.y.z.w\
  3. Restart Jellyfin.

🔧 Configuration

  1. Dashboard → Missing Episodes (or the sidebar entry) → Settings.
  2. Sonarr URL (e.g. http://sonarr.local:8989) and API key (Sonarr → Settings → General). Click Test Sonarr.
  3. (Optional) TMDB API key — get one free at https://www.themoviedb.org/settings/api. Click Test TMDB. Only needed for the TMDB Jellyfin mode.
  4. Scan source:
    • Sonarr — use Sonarr's data (best if Sonarr tracks every show).
    • Jellyfin — use Jellyfin's library. Pick a method: Virtual items / TMDB / Gap detection.
  5. Toggles: Only monitored, Ignore specials, Ignore unaired.
  6. (Optional) Auto-send missing episodes to Sonarr + interval.
  7. Save, then Scan now.

🔎 How each scan source works

Sonarr source

  • GET /api/v3/series for the series list.
  • GET /api/v3/episode?seriesId=N&includeImages=true per series.
  • Flags episodes where hasFile == false, honoring your toggles.
  • Thumbnails come from Sonarr's episode screenshot images.

Jellyfin / Virtual items

  • Queries Jellyfin for Episode items, expands the virtual ones (IsVirtualItem = true).
  • Requires Display missing episodes within seasons in your Jellyfin library settings + a library scan to create virtual items.
  • Best fidelity — full Jellyfin metadata + artwork.

Jellyfin / TMDB

  • For each series with a TMDB id, fetches GET /3/tv/{id} + GET /3/tv/{id}/season/{n}.
  • Diffs the canonical TMDB list against what's actually on disk in Jellyfin.
  • Catches whole missing seasons and trailing episodes that gap detection can't.

Jellyfin / Gap detection

  • Walks on-disk episode numbers per season, flags any {1..max} not present.
  • No external dependencies. Limited (can't see missing final episodes or whole missing seasons).

Enrichment

If Sonarr is configured AND you're running a Jellyfin-source scan, the plugin matches shows by TVDB or TMDB id and stamps them with Sonarr episode IDs. That's what makes Search buttons work in any mode.


🎯 Accuracy

Every Jellyfin-mode scan combines up to three sources of "present" data, fighting library drift and scattered file layouts:

  1. Jellyfin's library — non-virtual episode items it's already indexed.
  2. Filename parser — walks series.Path and extracts coordinates from S01E05, S01E05-E06, 1x05, etc. filenames. If Jellyfin's path is stale/empty, the parser retries against Sonarr's path (when Sonarr is configured) — Sonarr tracks the live location. Whichever yields more content wins.
  3. Sonarr hasFile enrichment (when Sonarr is configured) — for every entry still in the missing list, the plugin checks Sonarr's per-episode hasFile flag. Episodes Sonarr marks as downloaded are dropped from missing regardless of which folder they live in. Catches scattered-release layouts (per-season release folders etc.) that neither path walk covers. When Only monitored is on, episodes Sonarr has unmonitored are also dropped (you've told Sonarr not to track them, so they shouldn't inflate your missing count).

Pure-Jellyfin mode

If you leave both Sonarr and TMDB blank, accuracy is capped by what Jellyfin's library + the series folder tell us:

  • Virtual items — needs Display missing episodes within seasons enabled in the library and a metadata scan.
  • Gap detection — no library settings required, but can't detect missing seasons or episodes past the highest you have.

Either of those works standalone, but for libraries with files scattered across multiple folders you want Sonarr.

Limitations

  • Absolute-numbered anime filenames (e.g. Gintama - 042) don't match the S##E## regex. Re-rename or use Sonarr source.
  • A filename has to contain S/E markers. Pilot.mkv won't be detected.

📨 Sending to Sonarr

  • Per-episode — inline Search button on each missing episode row.
  • Per-season — header button on each season in the detail view.
  • Search all missing (one show) — bottom of the detail view.
  • Search all (across current filter) — toolbar button on the grid. Respects type, ignored, and text filters.
  • Auto-search — background worker runs at the configured interval, full scan, dispatches everything.

Every path hits POST /api/v3/command with a batched EpisodeSearch.


💾 What this plugin stores

All under the plugin's data folder (/config/plugins/Jellyfin.Plugin.MissingEpisodes/):

File Purpose
last-result.json Full result of the most recent scan. Lazily loaded on first request after restart.
history.json Rolling log of the last 25 scans (timestamps, totals, duration).

Plugin configuration (URLs, API keys, toggles) lives in Jellyfin's own plugin config XML, not here.

To wipe: stop Jellyfin, delete the files, restart. Next scan regenerates them. History can also be cleared from the UI.


🔒 Security

  • Admin-only. Every API endpoint carries [Authorize(Policy = "RequiresElevation")]. No anonymous endpoints.
  • No secret logging. Sonarr and TMDB API keys are passed to outbound HTTP calls only — never written to Jellyfin logs. Grepped every _logger.Log* call to confirm.
  • No secrets in plugin data files. last-result.json and history.json hold scan results only. API keys stay in Jellyfin's plugin config (as with any other plugin).
  • XSS-hardened. Image URLs from Sonarr / TMDB / Jellyfin are applied via element.style.backgroundImage = 'url(' + JSON.stringify(url) + ')' rather than interpolated into inline HTML — a hostile upstream response can't break out of the url() literal.
  • User-controlled strings escaped. All series/episode titles, network names, and paths rendered into the UI go through an escapeHtml helper before innerHTML assignment.
  • No path traversal. File walks only touch series.Path from Jellyfin's library manager plus (optionally) Sonarr's path for the same series. No user-supplied paths accepted by any endpoint.
  • Safe logging. Series titles and paths appear in log messages only at Debug level using structured parameters — ASP.NET Core's formatter prevents log injection.
  • No unbounded scans. Progress.InProgress gates concurrent scans; refresh endpoint returns HTTP 409 while a scan runs.

🛠️ Troubleshooting

"No scan yet" after a scan completed. Hard-refresh. Results persist to disk and a completion toast fires in the Jellyfin client, so a reload shouldn't lose them.

Search buttons greyed out. Either Sonarr isn't configured, or the selected shows have no Sonarr id yet. In Jellyfin-source mode the plugin enriches with Sonarr IDs only when a URL + API key is set.

Jellyfin / Virtual items returns 0 missing. Display missing episodes within seasons isn't enabled, or the library scan hasn't created virtual items yet. Enable it in Dashboard → Libraries → (library), run Scan Media Library, rescan. Or switch to TMDB mode.

Show reads as 0 episodes even though files exist. Fixed in 1.0.3 — the plugin now walks the folder and parses S##E## from filenames as a safety net. If you're on 1.0.3+ and still seeing this, check that the filenames actually contain S/E markers (not just an episode number).

"An error occurred while getting the plugin details from the repository." Harmless. Appears when the plugin was installed manually. To silence: add the manifest URL under Plugins → Repositories, then uninstall and reinstall from the catalog.

Rescan finished but the missing count didn't change. Rescan re-reads Sonarr/TMDB data — it doesn't wait for Sonarr to download anything. Give Sonarr a minute to grab a release, then rescan again.

Settings don't seem to save. Scan source / Jellyfin method segments auto-save on click. URL, API key, interval, checkboxes need the Save button. Reload to verify.


🏗️ Building from source

.NET 9 SDK required.

git clone https://github.com/ZL154/MissingEpisodesJellyfin.git
cd MissingEpisodesJellyfin/Jellyfin.Plugin.MissingEpisodes
dotnet build -c Release

DLL lands in bin/Release/net9.0/Jellyfin.Plugin.MissingEpisodes.dll.


❤ Support the project

Missing Episodes for Jellyfin is built and maintained in my spare time. If it's helping you keep your library tidy and you'd like to support ongoing development, any of these means a lot:

Not expected, just appreciated. Issue reports and clear bug reports are equally valuable.


📜 License

MIT — see LICENSE.

Not affiliated with Jellyfin, Sonarr, TMDB, or anyone else. Third-party plugin.

About

Jellyfin plugin: scan Jellyfin library or Sonarr for missing TV episodes, with a clean UI and optional auto-search integration.

Topics

Resources

Stars

3 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages