Writing a Jellyfin Plugin in .NET (Lessons)
· Jerwin Arnado
Archive note: this is a backdated post, written years later while rebuilding this site. It’s dated to the moment it covers, but the hindsight is real.
Last month’s itch has become a project. The watch-history questions my Jellyfin server couldn’t answer — what’s actually watched, what’s dead weight, who’s hogging the transcoder — turned into a working plugin prototype, and the journey there is this month’s post: a Laravel developer’s field notes from .NET country.
The plugin model, mapped for web devs
Jellyfin plugins are C# class libraries on .NET, loaded into the server process. Coming from the Laravel package world, the architecture maps cleanly once you find the legend: dependency injection everywhere (the most Laravel-familiar thing in the codebase — register services, constructor-inject them, feel at home), scheduled tasks instead of cron-and-artisan, configuration pages as embedded HTML served by the plugin, and the server’s REST API plus entity model in place of Eloquent. The unfamiliar parts are the runtime ones: your code lives inside the server’s process and lifecycle, so a plugin’s bug is the server’s bug — discipline the WordPress ecosystem learned painfully and kernel-driver vendors apparently didn’t.
The data layer was the genuine discovery. The Playback Reporting plugin — standard equipment on most serious servers — has been quietly logging every session to SQLite for years. My analytics questions didn’t need new instrumentation at all; they needed someone to read the existing ledger. Building on another plugin’s data store felt delightfully like the homelab’s own little open-data ecosystem: standing on the shoulders of volunteers, as the whole stack does.
C# through PHP eyes
The honest border-crossing report: modern C# is pleasant — and eerily convergent with where PHP’s own evolution has been heading. Properties with accessors (hello, property hooks), records for value objects (hello, readonly classes), first-class enums, LINQ as the collection pipeline Laravel’s Collections taught me to crave, and a type system that makes the IDE feel telepathic. The friction points are cultural, not technical: the ceremony of solutions and csproj files, NuGet’s relationship to versioning, and async/await as a mandatory idiom rather than PHP’s optional exotica. Two weeks in, I stopped translating and started thinking in it — the fastest language acquisition of my career, for a reason worth naming:
AI assistance has changed what “learning a stack” costs. The terminal agent drafted the project scaffolding, explained the unfamiliar idioms against my PHP priors (“this is your service provider; this is your migration”), and un-stuck every toolchain mystery in minutes that 2019-me would have lost evenings to. The verification discipline matters more in a language where my wrongness-detector is still calibrating — but as a learning accelerant, this is the strongest case I’ve personally experienced. The barrier between “my stack” and “any stack” is the thinnest it has ever been.
What the prototype already shows
Early findings from pointing queries at my own server’s history: a startling share of the library has zero plays — ever; a handful of users account for nearly all watch time; and transcode-heavy sessions cluster predictably (and expensively). In other words: the data was worth reading, the NAS-heater hypothesis is confirmed, and the disk-reclamation potential is measured in terabytes.
The prototype needs polish before it’s anyone else’s software — but the shape of something releasable is visibly in there, theme and all. (Working metaphor for a library full of unwatched media: a graveyard. We’ll see if it survives to the README.) If it ships, the Ko-fi-and-GitHub model gets its first project from this house. Watch this space.