Jellyfin BetaSeries

active · since 2026

A Jellyfin plugin that keeps a BetaSeries account up to date on its own: whatever you finish watching on your server gets marked watched, whatever you uncheck gets removed.

stack
  • ▸C# / .NET 10
  • ▸Jellyfin 10.11+ / 12+ plugin
  • ▸BetaSeries API, OAuth2
  • ▸HTML/JS config page in the dashboard
  • ▸xUnit, Moq
  • ▸GitHub Actions (tests, CodeQL, releases)
links

What it does

An episode reaches the end, and it's marked watched on BetaSeries with the actual playback timestamp. A pause, or a film abandoned a third of the way in, doesn't count: the plugin waits for the completion threshold already configured in Jellyfin.

It works the other way too, and by hand. Ticking a movie or an episode in Jellyfin sends it to BetaSeries; unticking it deletes it from the history over there. A whole season or a whole show propagates to all of its episodes at once.

A server usually means several people: create as many BetaSeries profiles as you need, attach one or more Jellyfin accounts to each, and every profile decides on its own whether it syncs movies, shows, live playback, manual marks. Two users can also share a single BetaSeries account.

One detail that doesn't show but matters: when you mark one episode, BetaSeries also marks every earlier one as watched. The plugin explicitly sends bulk=false, so a marked episode stays one marked episode, and the dates in your history stay the real ones.

How it's built

A .NET 10 DLL listening to the server's events (PlaybackStopped for playback, UserDataSaved for manual ticks) and a configuration page served inside the Jellyfin dashboard, with an OAuth2 popup so linking an account doesn't involve pasting a token around.

Most of the work is in the matching. Jellyfin and BetaSeries don't know about each other, so everything goes through the ids both of them store, and every id can be missing: a movie is looked up by TMDB, falling back to IMDb; a show by TVDB, falling back to IMDb, falling back to a title search validated against the TMDB id so a namesake doesn't come back instead; an episode by its TVDB id or by its season/episode position. A thirty-second cache absorbs events that arrive in bursts, and an "already watched" answer from the API counts as a success rather than an error.

HTTP calls sit behind one client interface, so the xUnit suite replays the whole resolution, marking and unmarking path without ever touching the real API.

Why

My show history has been on BetaSeries for years: I'd watch an episode, then go tick it somewhere else by hand, or more often three weeks later, trying to remember what I'd watched.

I moved from Plex to Jellyfin not long ago. Jellyfin is open source and takes plugins, so nothing stopped me from adding the missing piece myself. I don't write .NET, but with an LLM and the right specs, two hours end to end got me a working plugin.

Instead of poking at C# until it compiled, I wrote the specs, in detail, as if handing them to someone else: which event triggers what, the exact shape of the configuration, what the API returns in each odd case, and the places where the plugin should refuse rather than guess. An LLM wrote the code, I read it, corrected it, asked again, with a test per described behaviour. When you don't know the language, that's about the only way to check it does what was asked. Jellyfin Hue came out of the same method.

← back to projects