Tree / Self Hosting / Movies & TV Shows / Jellyfin / Experience & Ambience
Playnite Bridge
Shows a Playnite game library natively in Jellyfin, with tiles, detail pages, trailers and start buttons, without adding anything to Jellyfin's database.
- Status
- Green
- Type
- plugin
- Hours
- about 246
- Tokens
- about 226 M
Finished for my own use: the games run as a native library on my Jellyfin server, and the repository stays private for now.
In this article
Games in a place that has no idea what a game is
My games live in Playnite: Steam, GOG, Ubisoft, EA, Epic and everything else in one library. My films and series live in Jellyfin. I wanted both in one place, my games as a library in Jellyfin, browsed like my films, with posters, backdrops, trailers and extras. Not with fake video files that only exist to fool Jellyfin, and without writing a single game into Jellyfin's database.
That sounds like a display job. It is not, because the gap is structural on both sides:
- Jellyfin has no game item. There is no such type in 10.10.7, 10.11 or 12.1, so a game can only be delivered as something else.
- Jellyfin answers 404 for anything not in its database. Lists, detail pages and images all read from it.
- Jellyfin Web has no official plugin hook, so any web part has to be injected.
- Playnite does not merge versions. A game owned in three stores is three entries, and my library had 121 such groups.
- Playnite holds less than a film page shows: cover, background, icon, but no logos, no screenshots, no trailers.
- Start actions are not in Playnite's database. Each store's library code generates them at run time.
The goal in the requirements was one line: Playnite is the only source, the plugin reads, translates and shows. Setup should be install, set the Playnite path, done. And it had to be "aus einem Guss", all of a piece: not distinguishable from Jellyfin, using Jellyfin's own classes and components, different only where games really are different. It is view only. Nothing is edited, deleted or favourited from Jellyfin.
A games library that behaves like a film library
The plugin turns one Movies-type library with an empty folder into a games library. Open it and your Playnite games are there as poster tiles, with Jellyfin's own tabs, sorting, filters, paging, alphabet bar and view styles working on them. All Playnite stores and hand-added games appear. In my case that is 1,603 games, 1,536 visible, shown as 1,406 tiles.
Tiles and versions. One game, one tile. Versions from several stores are grouped by normalised title or a shared Steam AppID, by an own rule modelled on GOG Galaxy's tiles, because Galaxy's grouping comes from GOG's server, and a marker shows the store icons or just a number. Word lists decide about editions, roman numerals, demos and titles that must never group. On the detail page you switch the version in place, without a page rebuild, with soft fades. Every Playnite entry stays an independent game with its own metadata and artwork. Grouping exists in code only.
Suggestions and filters. A Suggestions tab computes rows such as last played, last added and "because you played". Collections are Playnite series. The filter dialog has game groups: Installed, Platform, Features, Completion Status, Genres, Parental Ratings, Tags, Years. Uninstalled games can be dimmed.
Detail page. It looks like a film's page: backdrop, logo, overview, year, play time, age rating, scores, and rows for series, genres, developer, publisher, platform, features, size and completion status. Every value is a link into the filtered library. Age ratings such as PEGI, USK and ESRB can follow Jellyfin's parental controls. Playnite's links appear as text or icons.
Starting and installing. Instead of a play button, there are buttons per store: Open in Playnite, Start via Playnite, Direct start, Install via Playnite, Install. Only those that fit the game's state show. From another device they can start the game on the PC running Jellyfin and Playnite, for admins only by default. Console entries from PlayStation and Xbox stay view only, because they have no start action.
Artwork and media. Covers, backdrops and icons come from Playnite, logos, banners and thumbs from Steam's local files or Playnite's extra metadata. Every image goes through Jellyfin's own image processor, so sizes and cache behave as for films. One folder per game holds the files under Jellyfin's names, and your own files always win. Trailers, extras and Steam store videos play in Jellyfin's player. Theme music and theme videos have their own players behind the page. Newer builds add screenshots and wallpaper rows, documents such as manuals opened in Jellyfin's own readers, a soundtrack row, and connections that link a game to actors and films in Jellyfin.
Sync. Sync Playnite copies Playnite's database files into the plugin's own folder while Playnite is closed, and a sync overwrites that copy. Sync Artwork fills the game folders. Both run from the menu, the admin page or a schedule. So there is a copy, but it is a one-way, read-only copy, and it is not in Jellyfin.
It needs Windows, with Jellyfin and Playnite on the same PC, and the File Transformation plugin by IAmParadox27. Native Jellyfin apps do not run the server's web page, so there is no start button in those. The main line was tested with Jellyfin Web 10.10.7, Playnite 10, Chrome and Windows 11. A build for Jellyfin 12 exists locally from the same source.
The idea that made it possible
The first version was the obvious one and it was thrown away. A browser overlay read Playnite's database and drew its own tiles, menus and detail page. It ran quickly, and it looked like an imitation. Own look, own menus, wrong filter menu, wrong detail page. It broke the all-of-a-piece rule at once.
What followed was research before rebuilding. Sessions read Playnite's data and store integrations, GOG Galaxy's client and repositories, and 22 badge and overlay plugins out of about 650 Jellyfin repositories. None of those plugins ran on 10.10.7 with platform logos on non-real items, so that part had to be ours. Requirements were then written line by line, each with a status: fixed, proposal or open. I went through them and decided.
The breakthrough was a small probe. One MVC action filter answers Jellyfin's item, library and image requests itself, in the shape Jellyfin expects. To Jellyfin Web, the games are virtual Movie items. Jellyfin's real pages draw them, so its tabs, sort, filters and paging simply work. The database did not get a single game: the item count before and after differed only by the two library folder entries. That pattern came from seeing how Jellyfin Enhanced uses MVC filters, and no code was taken from it. Everything since rests on that one decision.
How it was built, and with which models
This project was the pilot for building software almost entirely with agents. All code, specifications, audits, tests and tools came from Claude Code agents. I set the goal, decided requirements line by line, tested by eye and gave every go-ahead for the real server.
The first phase gave every feature the same line of fixed roles. Spec-writer, builder, security, approver and later bug-hunter and perf-analyst ran on Opus. Code, UX, UI design and measuring roles ran on Sonnet. Only the builder may write product files, in its own worktree. Four read-only reviewers look in parallel, with at most two fix rounds, then an approver decides. A controlling chat supervised, solved escalations itself, and stopped for anything touching a fixed requirement, publishing or the real server, which came to me as a list of open questions. That line produced the whole plugin at speed: list, images, detail page, tabs, filters, stores, grouping, start buttons, admin page, sync, languages, extras, parental controls.
It was fast, and then it hit a wall. An audit of the release candidate said not ready. Then, on my own server, I reported flicker on version switches, a vanishing header and slow page changes. A look at the process itself found the reason: the definition of done had no time dimension. Every role read code. Every acceptance list compared settled states. A settled state cannot show a flicker. Reviewers were the same kind of reader as the builder.
So the method changed. A measuring bench now drives the web client in a browser and records frames, layout shifts and leaks. A scenario list of my real paths came with budgets, and roles that exercise the product joined the ones that read it. Later rounds dropped the fixed line altogether, with a lead session and separate builders, auditors, counter-auditors and testers. Each topic got a spec, an audit and a separate counter-audit. Long feedback lists from me were built in stages.
Every path has to tell the same lie
Pretending to be Jellyfin. Faking an item is only easy until every path has to agree. Lists, images, playback, the Suggestions rows and the filter dialog all ask in different ways. A test tool grew to over 2,400 query cases to keep those answers straight on both target frameworks. One incident came from the inverse question, what happens to IDs that are not ours. Our rows were marked by one last byte, and a random ID hits that 1 time in 256, so some real Jellyfin folders were answered as empty. Spec, audits and tests had never asked. A central ID gate fixed it, and one million random IDs now run against it.
Other plugins on the same page. My own ArtworkPlus and VideoOSD work on the same elements. The first coordination between those projects went badly: greyed fields did not follow, values had to be asked for by message, deploys took several rounds. It got better only with frozen written rules and a joint sync. The Bridge offers a small link class that ArtworkPlus finds by reflection. A Settings From choice on the admin page decides who draws game artwork. On ArtworkPlus, the Bridge greys its own display options and stays off named elements, so two plugins never touch one thing. My rule was: the same features with and without ArtworkPlus.
Data that does not behave. Reading Playnite's database directly seemed elegant and was the first audit's recommendation. It turned out Playnite holds all its files in exclusive mode while it runs, so even copying fails. That is why sync exists and why Playnite has to be closed. Steam sometimes stores a blank logo. Start actions had to be rebuilt per store.
Admin complexity. There are over 700 settings with dependencies between them. A check now walks over 15,000 states of the admin page to prove that greying and values agree.
Testing at this size. A round with ten parallel testers, next to another project's jobs, overloaded the machine. The tools themselves also need maintenance: several reported failures in later rounds were faults of the test scripts.
What held, and what I threw away
- Worked: the virtual items. Jellyfin's database stays clean, and removing the plugin leaves no game behind.
- Worked: the requirements file with a status per line. It stopped agents from re-deciding what I had already decided.
- Worked: read-only reviewers in parallel. They found real faults in code, and the supervising chat kept their wishes inside the spec.
- Worked: frozen rules between the plugins, and the joint check with the Playnite library project, which showed no differences for cover, background, icon and logo over all 1,536 versions.
- Worked: measuring transitions. Layout shift on a version switch fell from 0.073 to 0.0014 with no flash frames, and the switch settles in under half a second.
- Partly: the fixed line of roles. Excellent for building a lot quickly from a clear spec, blind to everything that only exists while the page runs. Its first review model could not have caught what I saw on screen.
- Partly: Jellyfin 12. The port was built and walked through on both lines, but its acceptance list is not complete, and the newest features were never tried there.
- Did not work: the overlay prototype, direct database reads, and any library type other than Movies, because other list pages lack the tabs, sort and filter.
- Did not work: my own early goal of no database copy at all. I replaced it myself when the facts were in.
Confidently done, long before it feels right
My conclusion is simple. Agents can build a great deal, and they will be confidently done long before the thing feels right. The expensive part is not producing code. It is defining done so that it can fail, and keeping a person in the loop who actually uses it. The big quality jump in this project came from something I saw on my own server, and then from turning that into a measurement.
The plugin runs on my real server, and I use it as my games library. The project has grown to about 35,000 lines of C#. Newer work, such as collections and the soundtrack fallback, is built and still has to pass live tests.
The repository is private. Before any publication, store icons and link badges need a rights check and the source needs cleaning. Games are not in Jellyfin's global search or on the home page yet, and starting from a remote device has not been proven. That is why it counts as finished for me and not released for you.
Connected
Next: X-Ray