Plugins and clients for Jellyfin. This repository holds the plugin manifest, the design system, and the numbers the clients are held to.
Flowfin is not affiliated with the Jellyfin project.
Three steps.
-
In your server's Dashboard, open Plugins and then the Repositories tab.
-
Add a repository. Repository Name is yours to choose and is only a label. Repository URL is this, exactly:
https://flowfin.dev/manifest.jsonJellyfin warns before it accepts a repository that is not its own, and the warning is worth reading rather than clicking past: a third-party repository may hold unstable or malicious code and may change at any time. That is true of this one as much as of any other. What can be said for it is that the file is generated from the plugin releases rather than written by hand, and that every entry names the repository its archive was downloaded from, so anything offered here can be read at its source before it is installed.
-
Go back to the plugin list. What this repository offers appears there beside whatever the server already had.
What it offers today is one plugin, Requests, and that plugin's own entry says
it is under development and adds nothing a user can see yet. One entry is what
this address serves rather than a catalogue that failed to load, and telling
those two apart is the next section.
The address itself is served from a domain this project holds rather than from a
github.io name, so the hosting and even the account behind it can change later
without breaking a single installation. An address that moves breaks every
install silently: no error, the plugins simply never update again.
A Jellyfin server says nothing when a plugin repository does not work. It shows an empty list, and it shows the same empty list for three different problems. None of the three produces an error message anywhere, so the list on its own says nothing about which one it is.
Run these on the machine the server runs on rather than on the machine you are reading this from. A server behind a firewall, or on a network with a resolver of its own, can fail all three while your laptop succeeds, and nothing in the interface shows that difference.
Whether the address answers at all.
curl -sS -o /dev/null -w "%{http_code}\n" https://flowfin.dev/manifest.json
200
200 is the answer to want. Any other number, or an error from curl instead of a
number, means that machine cannot reach the address, and the problem is between
it and the network rather than in Jellyfin or in the file.
Whether what comes back is a catalogue.
curl -sS https://flowfin.dev/manifest.json | python3 -m json.tool > /dev/null ; echo "exit=$?"
exit=0
Silence and a zero exit mean it parsed. An error means something answered with
bytes no server can read as a catalogue, which is what a captive portal, a
filtering proxy and a cached error page each produce with a 200 in front of
them.
Whether anything in it fits your server.
curl -sS https://flowfin.dev/manifest.json \
| python3 -c "import json,sys; [print(p['name'], v['version'], 'needs', v['targetAbi'], 'or newer') for p in json.load(sys.stdin) for v in p['versions']]"
Requests 0.1.0.0 needs 10.11.0.0 or newer
Every version entry names the oldest Jellyfin it will install into, and your server's own version is on its dashboard. A server older than every line that command prints sees an empty list, and that is the catalogue working rather than failing: the entries are there and none of them fits.
Check them in that order. The first is the one that is wrong most often and the cheapest to answer, and a failure at any of the three makes the two after it unreadable.
The catalogue is rebuilt from the plugin releases once a day, and a rebuild can also be asked for on the day a release lands. That is the half of the answer with a number on it.
The other half has none, and inventing one would be promising something this project does not control: the rebuilt file still has to reach the address, and the host that serves these pages publishes on its own timing. So the honest answer is a day for a release to be picked up, plus a publication that is usually short and is not guaranteed. A version you know exists and that has not appeared a day later is worth asking about rather than waiting out.
| Path | What it is |
|---|---|
docs/ |
The published site: manifest, plugin list, design system |
docs/design-system.html |
Colour, type, tiles, focus, and the speed budget |
manifest/ |
The generator that builds manifest.json from the plugin releases |
The manifest is generated, never edited by hand. It is built from the release artifacts of each plugin repository, and a check refuses a build that does not list a current release for every Jellyfin version line it claims to support. A manifest maintained by memory is a manifest that is wrong the first time somebody is busy.
Native clients, one per platform, sharing a core rather than a codebase. The
design system in docs/ is what makes eleven clients look like one product.
Two things are settled there and worth repeating here, because they are the parts that get quietly dropped first:
Nothing waits visibly. No spinner inside a tile, no layout shift when an image arrives, no reflow. The speed budget is written as numbers that a build can miss - focus change under 80 ms, no dropped frames at 60 fps across 200 tiles, first usable tile under 1.2 s from a cold start.
Missing artwork is a design case, not an error case. Netflix curates its imagery; a personal library has whatever the metadata provider happened to hold. A tile wall with three grey holes looks worse than a list, so the empty tile has a shape of its own.
The interface is neutral. Content is the only colour, and one accent marks one thing: what has focus.
That accent is configurable, with presets per deficiency rather than a single "accessible" mode - because which hue works depends on which cone type is missing, and the hue that serves the two common forms best is the wrong one for the rare third. Removing colour entirely is correct only for achromatopsia; for everyone else it discards a signal their eyes could have read.
No state depends on hue alone, whatever the preset.
Nothing about the server, the people using it, or what is in their libraries.
Giving a Jellyfin server a plugin repository address means the server fetches a
file from that address, on a schedule the server decides. The request carries
what any request carries, the address it came from and whatever the network puts
on it, and the file that comes back is the same file for every server. There is
no value in it that differs between operators, so nothing in the exchange says
which server asked or what it holds. decisions/data-posture.md is the rule and
lists what it rules out, including the identifier per install that is the usual
shape this goes wrong in.
Somebody sees the requests, and it is worth saying who rather than implying
nobody does. The site and the file are served by GitHub Pages, from the docs
directory of this repository:
gh api repos/Flowfin/hub/pages --jq '{cname, source: .source.path, https_enforced}'
{"cname":"flowfin.dev","https_enforced":true,"source":"/docs"}
Run 2026-08-09. Serving a file means receiving a request, so GitHub receives every one of them, and whatever it keeps is GitHub's under GitHub's own terms. This project has no access to those records and adds nothing on top of them: no analytics, no counter of its own, no log. An operator who has to answer for their deployment can take that as the whole of it, because there is no second party here to find out about later.
The plugins listed in the catalogue are separate projects in separate repositories. What each one does with data is its own to describe, and listing something is not a statement about its behaviour any more than it is about its licence. The catalogue entry names the repository; read it before installing from it.
Personal data stays on the operator's own host unless the operator sends it somewhere on purpose, and nothing in this catalogue can start that. A plugin installed from it can, and it looks like something configured rather than something that happens: another server's address entered in a setting, an invitation issued to somebody outside the household, a synchronisation target pointed at a machine that is not yours. If none of that has been set up, nothing has left the host. Whether a particular plugin offers any of it is that plugin's own documentation.
The GNU Affero General Public License, version 3 or any later version. The full text is in LICENSE, and the "or any later version" is the option the licence's own application notice offers rather than an addition to it.
The detector reads it:
gh api repos/Flowfin/hub --jq '.license.spdx_id'
AGPL-3.0
Run 2026-08-09.
Every path in this repository, with no exception and no second licence anywhere in the tree. That is worth saying out loud because this tree holds three kinds of thing with three different audiences, and a reader who knows that will look for a boundary:
| Path | What it is | Under |
|---|---|---|
manifest/, internal/, main.go |
The generator, which is a program | AGPL-3.0-or-later |
docs/ |
The published site, including the design system | AGPL-3.0-or-later |
decisions/, *.md |
The documents | AGPL-3.0-or-later |
sources/ |
The declared source set, which is data | AGPL-3.0-or-later |
The design system is prose and numbers other projects are invited to adopt, and adopting them means following what they say rather than copying the file, which the licence does not reach. Copying the file, or a client built from it, is a derivative and does.
Whether the published pages and the design system should carry different terms
from the generator is entry 1 of #1 and is open. A single LICENSE at the root
reads as covering the whole tree, which is what the table above states, so a split
would be a change to this rather than a reading of it.
The plugin repositories this catalogue lists are separate repositories under their own terms. A catalogue listing something is not a statement about its licence.
See NOTICE.md for the intended-use notice.