Note
Part of Flowfin. It works with any Jellyfin server, and with the Flowfin clients.
Media requests for Jellyfin as first-class server objects. A user asks the server for a film or a series it does not have, an administrator sees the ask in a queue and answers it, and the request keeps its own state and history on the server rather than in somebody's chat log.
Three things this is regularly expected to do and does not. Each was decided rather than left out, which is why they are here rather than in an issue nobody finds.
It does not fetch media. Approving a request moves a record from one state to another and reaches nothing that downloads anything. What makes the film turn up is whatever the operator already runs, and this plugin notices it arriving by watching the server's own library.
It does not manage a download client. There is no connection to one and none is planned. Where an operator runs an external request service, an approved request can be handed to that service and the service is what talks to whatever fetches; that handover, and the words the two sides have to agree on, are in docs/bridge.md.
It ships no way to find a title the server does not have. On a server with no
browsing sibling plugin installed there is no gesture on a television client
that creates a request at all. The reason is that the sibling owns the catalogue
and this plugin calls no metadata source, and that is refused rather than
promised: no-call-to-a-metadata-source in tools/opengrep/rules.yaml refuses
the server's provider interfaces and the addresses a plugin would otherwise call
directly, and carries the fixture it is watched refusing.
Four releases exist, two per server line, and the manifest under Flowfin's control serves two of them:
gh release list --repo Flowfin/jellyfin-plugin-requests --json tagName --jq '.[].tagName'
0.3.0.0-jf12-stable
0.3.0.0-stable
0.2.0.0-stable
0.1.0.0-stable
curl -sS https://flowfin.dev/manifest.json | jq -r '.[] | select(.name == "Requests") | .versions[] | "\(.version) \(.targetAbi)"'
0.2.0.0 10.11.0.0
0.1.0.0 10.11.0.0
Read on 2026-09-03, after both 0.3.0.0 tags were published. A server that has
added the address is offered 0.2.0.0 until the manifest moves, and nothing on
this board moves it: the release route here publishes a GitHub release and feeds
no catalogue, which docs/RELEASING.md says about itself.
The address an operator adds, what the entries carry and the checksums read back
against the archives are in docs/catalogue.md, which is the
authority for all of it, and what this tree does with the published manifest is
read it back against the releases once a day.
Three things belong here rather than behind the link, because they decide whether this is worth installing.
The official Jellyfin catalogue does not carry this and will not. That list is filled by enumerating one organisation's repositories, this repository does not move into it, and the price of that decision is that an operator reaches this plugin only by being told the address.
Both entries the manifest serves carry the 10.11 line's package, and the 12.0
line's release is not in it. A 0.3.0.0 package naming targetAbi 12.0.0.0
exists as a release, read from the metadata published beside it:
gh release download 0.3.0.0-jf12-stable --repo Flowfin/jellyfin-plugin-requests --pattern 'requests_0.3.0.0.zip.meta.json' --dir jf12
jq -r '"\(.version) \(.targetAbi)"' jf12/requests_0.3.0.0.zip.meta.json
0.3.0.0 12.0.0.0
Until the manifest carries an entry for it, a server on the 12.0 line is offered
the net9.0 build rather than nothing. That comparison is read at the server's
own source rather than assumed, on the same page.
A published release has been installed on a server of each line, once each.
Run 33744830699, at 2e83259, downloaded requests_0.3.0.0.zip from each
release, checked each against the digest published beside it, and both servers
answered that the plugin was Active at 0.3.0.0:
gh run view 33744830699 --repo Flowfin/jellyfin-plugin-requests --log | grep -o 'Requests loaded and answered on .*'
Requests loaded and answered on jellyfin/jellyfin:10.11.11 (net9.0)
Requests loaded and answered on jellyfin/jellyfin:12.0-rc4 (net10.0)
The 12.0 server was a release candidate reporting 12.0.0; no released 12.0
server has run this. Before that run, 0.2.0.0 had been installed the same way
on 10.11.11 alone, in run 33357925338 at 501a943.
On the floor server it claims, 0.2.0.0 does not load. build.yaml
says targetAbi: "10.11.0.0", which names the server 10.11.0, and putting
requests_0.2.0.0.zip on that server reports
Requests is NotSupported rather than Active - run 33358848505. Jellyfin sets
that status in one place, on a failure to load the assembly's types. What that
server does not carry is not a member, it is the version: the archive's five
Jellyfin references are stamped 10.11.11.0 and 10.11.0 carries all five at
10.11.0.0, and a reference above what the host carries does not bind. Which of
the two moves, the SDK the package is built against or the floor it claims, is
answered on #360: the package is built against the floor from now on and the
floor stands, because moving the claim would drop every server below 10.11.11
without saying so. That answer does not reach a release that already exists.
Do not install 0.2.0.0 on a 10.11.0 server: it will not run. Whether
0.3.0.0, the first release built against the floor, loads on that server is
#382's measurement and is not claimed here.
Two further things the install run does not cover: the bytes are copied into the
plugin directory rather than fetched and unpacked by the server itself, and no
server has installed this from the manifest, which does not yet carry 0.3.0.0
for either line.
Nothing here should be pointed at a server you care about.
The plan is on the issue tracker, cut into milestones. Each issue says what is wrong, what the evidence is and what has to be true for it to be closed.
Two server generations are claimed, and each gets its own package because a plugin compiled for one runtime does not load on the other. Claimed is not published: the section above says which of the two the releases carry.
| Line | Runtime | Packaging metadata | Oldest server claimed |
|---|---|---|---|
| Jellyfin 10.11 | .NET 9 | build.yaml |
10.11.0.0 |
| Jellyfin 12.0 | .NET 10 | build-jf12.yaml |
12.0.0.0 |
Those numbers are read out of the two files rather than restated from memory, so an edit to either shows up as a difference between this table and what the command prints:
git grep -nE '^(version|targetAbi|framework):' -- build.yaml build-jf12.yaml
build-jf12.yaml:13:version: "0.3.0.0"
build-jf12.yaml:15:targetAbi: "12.0.0.0"
build-jf12.yaml:16:framework: "net10.0"
build.yaml:5:version: "0.3.0.0"
build.yaml:10:targetAbi: "10.11.0.0"
build.yaml:11:framework: "net9.0"
The framework of each line is what the project file multi-targets against. The
version both files carry is one number held in one place, and a test refuses a
disagreement between it and the assembly rather than trusting three copies to be
edited together:
git grep -n '<PluginVersion>' -- Directory.Build.props
Directory.Build.props:42: <PluginVersion>0.3.0.0</PluginVersion>
That test is PluginVersionMatchesThePackagingMetadata in
Jellyfin.Plugin.Requests.Tests/PackagingMetadataTests.cs, and
docs/versioning.md is where the scheme and what each field
means to somebody who already has this installed are written down.
An assembly built for each line has been installed on a server of that line and
reported Active by the server itself. The transcript of that run, the images
it ran against and what it does not cover are in
docs/testing.md. What has not been tried is the packaged
install path an operator would use, which is a later milestone.
What is supported, what is only expected to work, and what a downgrade does to the requests a server already holds are in docs/compatibility.md.
No cell of the reach matrix in docs/surface.md has been checked against a real client, and the channel now on the mainline has not been browsed from one.
That sentence is the whole claim this file makes about client reach, and it is the sentence the matrix opens with. The matrix itself, a row per client family and what a user could do there, is in docs/surface.md. It is not repeated here, because two copies of a table drift and the reader then has two answers.
One line of it is worth having before installing anything. On a server with no browsing sibling plugin there is no way to ask for a title the server does not have from a television client at all. This plugin ships no title search of its own, and that was decided rather than overlooked.
dotnet build
With no framework argument that builds both targets. The .NET 10 SDK builds both, and a machine with only the .NET 9 SDK has not been tried.
GPL-3.0. The full text is in LICENSE, and it is the authority for the terms, the warranty disclaimer and the limitation of liability.
A plugin is compiled against the server's libraries and ships as an assembly that links them, so the terms those libraries carry reach the compiled result whatever the source says. The packages this one compiles against declare GPL-3.0-only, which is what makes GPL-3.0 the licence here rather than a preference:
grep -h '<license type' ~/.nuget/packages/jellyfin.controller/*/jellyfin.controller.nuspec | sort -u
<license type="expression">GPL-3.0-only</license>
The server's own repository carries GPL-2.0 in its licence file, so those two do not agree, and docs/catalogue.md is where both are measured and where what follows from them is written out.
See NOTICE.md for the intended-use notice.