Open-source project
IAmParadox27/jellyfin-plugin-file-transformation avatar
IAmParadox27/jellyfin-plugin-file-transformation

File Transformation: letting Jellyfin plugins rewrite jellyfin-web at runtime

Jellyfin plugin that allows other plugins to manipulate the content served by jellyfin-web without modifying the web source directly.

566 stars31 forksC#GPL-3.0

At a glance

What is it?
File Transformation is a Jellyfin plugin that intercepts the web assets the server serves and lets other plugins patch them with a callback, so that a plugin can add pages or home sections without anyone editing the jellyfin-web files by hand. It suits a plugin author who wants to modify the client without forking it, and the cost is a reflection-based registration path, since Jellyfin loads plugins into separate load contexts and the library cannot be referenced directly.
Who is it for?
Adopt File Transformation if you are writing a Jellyfin plugin that needs to change what the web client serves, since it gives you a callback hook over the asset content without asking installers to patch files.
Can I use it commercially?
Yes, with conditions. GPL-3.0 is a copyleft licence: if you distribute software that includes it, you must release that software's source code under the same licence. Running it internally without distributing it does not trigger that obligation.
Is it still maintained?
Yes. The repository last received commits 11 days ago.
What is it written in?
Mainly C#, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on October 23, 2026, and from our analysis. They are not legal advice.

Editorial analysis

A plugin whose only job is to be a hook

File Transformation is a Jellyfin plugin that can be used to modify the served jellyfin-web content without having to modify the files directly.

That is the entire product in one sentence, and it is a small product with a wide reach. A plugin that wants to add a page to the web client, or rearrange the home screen, would normally have two options. It could ship modified copies of the web files and tell users to replace them, which breaks on the next Jellyfin update and leaves the server in a state nobody wants, or it could ask upstream for a change to jellyfin-web itself and wait.

This plugin takes a third route. It intercepts the content the server serves to the browser, hands it to a callback that another plugin registers, and serves the result. The web files on disk are never touched, and the modification is applied every time the asset is requested.

The original code and concept came from a pull request to the Jellyfin repository by JPVenson, and a lot of the code here is unmodified bar the author's personal code standards. That provenance is worth knowing, because it means the mechanism was designed by someone working inside the Jellyfin project rather than reverse engineered from outside.

The use cases are visible in the author's other plugins, plugin-pages and home-sections, both of which take advantage of this. The first adds pages to the client and the second restructures the home screen, and neither of them can do that without a way to change what the web client loads.

The project is GPL-3.0 licensed, written in C#, and the release history shows a recent 3.x line with 3.0.2.0 from 2026-10-12, 3.0.1.0 from 2026-09-15 and 3.0.0.0 from 2026-09-08.

The argument against asking installers to patch files

The plugin argues its case as a question and then answers it, and the answer is the reason to use it.

The question is why would I use this rather than just asking installers to change the files? The answer given is that this plugin is non destructive and allows multiple plugins to manipulate the served data. The install remains clean and free for users to update their Jellyfin server whenever they want.

Non destructive is the first half of the value and it is the one that matters most in practice. A server that has had its web files replaced is a server whose next update might break in a confusing way, and the person who has to fix it is an administrator who did not make the change. The plugin removes that class of problem entirely, because the modification is a layer applied at request time and the underlying files stay as the server shipped them.

The second half, multiple plugins being able to manipulate the served data, is the part that a manual patching approach cannot offer at all. Two plugins that both want to change the same web file cannot both be satisfied by asking users to apply two sets of edits to a file that ships with the server. With a registration mechanism, each plugin registers its own transformation and the plugin being patched applies them.

That is the real reason to prefer a hook over a patch. The alternative does not scale past one plugin, and it also does not survive one server update.

For the user of the plugin, the cost is close to zero. Installation is two steps, adding the manifest as a plugin source repository on the server and then installing File Transformation from the list, and no configuration is required. There is nothing to set up and nothing to get wrong.

Registering a transformation without a compile-time reference

The interesting engineering in this plugin is the registration path, because of a constraint that is stated plainly.

Due to issues with Jellyfin's plugins being loaded into different load contexts, this cannot be referenced directly. A plugin author therefore cannot add a package reference and call a method. Instead the registration goes through reflection, and the payload is a small JSON object.

The first step is to prepare the payload:

json
{
    "id": "00000000-0000-0000-0000-000000000000", // Guid
	"fileNamePattern": "", // Regex Patterm for the file to patch
	"callbackAssembly": GetType().Assembly.FullName, // Example value is a string from C# that should be resolved before adding to json
	"callbackClass": "", // The name of the class that should be invoked from the above assembly
	"callbackMethod": "" // The name of the function that should be invoked from the above class
}

The fields describe which file to patch and which function to call. The file name pattern is a regular expression, the assembly is identified by its full name, and the class and method names identify the callback. The id is a GUID.

The second step is to send that payload to the file transformation assembly, and this is where the reflection is:

csharp

Assembly? fileTransformationAssembly =
	AssemblyLoadContext.All.SelectMany(x => x.Assemblies).FirstOrDefault(x =>
		x.FullName?.Contains(".FileTransformation") ?? false);

if (fileTransformationAssembly != null)
{
	Type? pluginInterfaceType = fileTransformationAssembly.GetType("Jellyfin.Plugin.FileTransformation.PluginInterface");

	if (pluginInterfaceType != null)
	{
		pluginInterfaceType.GetMethod("RegisterTransformation")?.Invoke(null, new object?[] { payload });
	}
}

The shape of it is worth understanding. The search is over all load contexts, because that is the whole problem: the assembly is not in your context, so you go looking for it everywhere. The type is then resolved by its full name and the method invoked with the payload, and the null conditional on the method call means a missing method is a no-op rather than a crash.

The callback itself receives an object with the current file contents:

json
{
  "contents": "" // String containing the current state of the file being requested.
}

The comment on that field says it all: the contents are the current state of the file being requested. The callback gets the live content, transforms it, and the result is served.

For a plugin author this is a workable but ungainly API. Everything is strings and reflection, there is no compile-time checking that the class or method exists, and a typo in the class name fails silently because of the null conditional. It is the price of the load context constraint, and the alternative would have been forking the server.

One version compatibility, and what to do when the catalogue is empty

The FAQ entry about an updated Jellyfin and an empty catalogue is the most practically useful thing in the documentation, because it is the first question anyone new will hit.

The likelihood is that the plugin has not been updated for that version of Jellyfin, and the plugins are strictly 1 version compatible. Please wait until an update has been pushed. If you can see the version number in the release assets then please make an issue, but if it is not in the assets, please wait.

That is a precise diagnostic procedure, and it is worth following exactly. The presence of the version number in the release assets tells you whether a build for your server version exists. If it does, something has gone wrong and an issue is warranted. If it does not, the answer is simply to wait, because the author has stated plainly that they will update when they can and that they know Jellyfin has updated.

Strict one-version compatibility is worth understanding as a design consequence rather than as sloppiness. A plugin that rewrites served web content has to match the client it is patching, because the file it is transforming changes shape between Jellyfin releases. A transformation written against the 10.10 client and applied to the 10.11 client may produce a page that does not load. Pinning to one version is the honest way to keep that from happening.

For a plugin author, this has a direct consequence. The contribution guidelines ask that the plugin compiles with 10.10.7 and the latest version of JF, and the author has already made a start on 10.11.0 and intends to release an update once that version is officially released. So the update cadence follows Jellyfin's own release cadence, and the two are locked together.

For an administrator, the practical advice is to install the plugin when it appears for your version and not to expect it to follow a server update automatically.

How the project is run and what it asks of contributors

The repository is small, with a `src/` directory for the plugin source, a LICENSE file, a README and a SECURITY.md, plus a `.github/` directory. The plugin itself is built as a Jellyfin plugin, and it is distributed from a plugin repository at iamparadox.dev rather than from GitHub releases alone, since the installation instructions point at the manifest URL on that domain.

The development update section from August 2025 explains how the author's wider plugin work is being organised, and it is worth reading because it describes the process you will deal with as a user or a contributor.

Bugs and features have been split into different areas. Feature requests go to a separate site, features.iamparadox.dev, where you sign in with GitHub and make a request. Bugs stay on the relevant GitHub repository and get added to a project board once the author has seen them. The stated reason is that the author was struggling to know when issues were made, and a plan is described to monitor a particular board view for new issues and send a notification, so that responses can be faster.

The same section is candid about working patterns. The author describes being momentum based in their personal coding life, warns that the projects may appear dormant, and asks for patience where there is no update for a couple of weeks. That is a fair warning from someone running several plugins for a media server, and it applies to how quickly you should expect a response.

The contribution guidelines are unusually specific for a plugin of this size, and the reasons are practical. Do not commit whitespace changes, because that hides the real diff. Do not use `var` unless you have to, due to differing namespaces between JF versions. Please at least check the plugin compiles with 10.10.7 and the latest version of JF. Put braces on new lines and use them even for one line statements. And blend in with the codebase, since the author uses a mutated Hungarian notation for their coding style and will ask for it to be followed.

There is one more instruction that is unusual enough to be worth quoting. Please declare your AI usage, if any, and if you have used AI for your change, please also declare your coding experience without AI. The author states plainly that this will not impact whether the change is merged, and the reason given is that it lets them review the change properly to ensure nothing slips through the net. That is a reasonable request for a reviewer who wants to know how much of a patch to trust, and it costs a contributor one paragraph.

Editorial conclusion

Adopt File Transformation if you are writing a Jellyfin plugin that needs to change what the web client serves, since it gives you a callback hook over the asset content without asking installers to patch files. Do not adopt it if your change belongs in the server rather than the client, because it only touches served web content, and check first which Jellyfin version you are on, because the plugins are strictly one version compatible and a missing catalogue entry usually means the build for your server version has not been pushed.

Frequently asked questions

What does the File Transformation Jellyfin plugin do?

It modifies the served jellyfin-web content without having to modify the files directly. Other plugins register a callback with it, and the callback receives the current contents of the file being requested and returns the transformed content, so the web files on disk are never edited.

How do I install File Transformation on my Jellyfin server?

Add https://www.iamparadox.dev/jellyfin/plugins/manifest.json as a plugin source repository on the server, then find File Transformation in the list and install it. No configuration is required, and the install is non destructive so the server stays free to update whenever the user wants.

How does a plugin register a transformation with File Transformation?

You cannot reference the plugin directly because Jellyfin loads plugins into different load contexts, so you use reflection. Prepare a JSON payload with an id, a fileNamePattern regex, the callbackAssembly full name, callbackClass and callbackMethod, then find the assembly with AssemblyLoadContext.All, resolve Jellyfin.Plugin.FileTransformation.PluginInterface and invoke RegisterTransformation with the payload.

Why is the File Transformation plugin missing from the catalogue after I updated Jellyfin?

The plugins are strictly one version compatible, so the most likely reason is that the plugin has not been updated for your version of Jellyfin yet and you should wait. If the version number for your release is listed in the release assets then it is worth opening an issue, and if it is not there yet, wait for the author to push it.

Which other plugins use File Transformation?

The author's own plugin-pages and home-sections plugins both take advantage of it, since both need to change what the Jellyfin web client serves without users editing the web files. The original code and concept for the plugin came from a Jellyfin pull request by JPVenson.

Is File Transformation still being maintained?

The repository is not archived and the last push was on 2026-10-12. Recent releases are 3.0.2.0 from 2026-10-12, 3.0.1.0 from 2026-09-15 and 3.0.0.0 from 2026-09-08, the licence is GPL-3.0, and the author states an intention to keep updating the plugins for each new Jellyfin release.

Official sources

  1. IAmParadox27/jellyfin-plugin-file-transformation on GitHub
  2. Issues
  3. License: GPL-3.0
  4. README
  5. Releases
Add this badge to your README

If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.

Add this badge to your README

markdown
[![Hysen Labs](https://hysenlabs.com/badge/iamparadox27-jellyfin-plugin-file-transformation.svg)](https://hysenlabs.com/projects/iamparadox27-jellyfin-plugin-file-transformation)

Was this useful?