The JavaScript Injector plugin for Jellyfin allows you to inject multiple, independent JavaScript snippets into the Jellyfin web UI. It provides a powerful and easy-to-use configuration page to manage all your custom scripts from one place.
-
Multiple Scripts: Add as many custom JavaScript snippets as you want.
-
Organized UI: Each script is managed in its own collapsible section, keeping your configuration clean and easy to navigate.
-
Enable/Disable on the Fly: Toggle individual scripts on or off without having to delete the code.
-
Immediate Injection: The plugin injects a loader script into the Jellyfin web UI upon server startup. Your custom scripts are loaded dynamically, and changes take effect after a simple browser refresh.
-
Plugin Support: Other plugins can register their own JavaScript snippets programmatically using the provided service interface.
- In Jellyfin, go to Dashboard > Plugins > Catalog > ⚙️
- Click ➕ and give the repository a name (e.g., "JavaScript Injector Repo").
- Set the Repository URL to:
Important
If you are on Jellyfin version 10.11
https://raw.githubusercontent.com/n00bcodr/jellyfin-plugins/main/10.11/manifest.json
If you are on 10.10.7
https://raw.githubusercontent.com/n00bcodr/jellyfin-plugins/main/10.10/manifest.json
- Click Save.
- Go to the Catalog tab, find JavaScript Injector in the list, and click Install.
- Restart your Jellyfin server to complete the installation.
Important
If you are on a docker install it is highly advisable to have file-transformation at least v2.2.1.0 installed. It helps avoid permission issues while modifying index.html
If you're running Jellyfin through Docker, the plugin may not have permission to modify jellyfin-web to inject the script. If you see permission errors such as 'System.UnauthorizedAccessException: Access to the path '/usr/share/jellyfin/web/index.html' is denied. in your logs, you will need to map the index.html file manually:
-
Copy the index.html file from your container:
docker cp jellyfin:/usr/share/jellyfin/web/index.html /path/to/your/jellyfin/config/index.html
-
Add a volume mapping to your Docker run command:
-v /path/to/your/jellyfin/config/index.html:/usr/share/jellyfin/web/index.html -
Or for Docker Compose, add this to your volumes section:
services: jellyfin: # ... other config volumes: - /path/to/your/jellyfin/config:/config - /path/to/your/jellyfin/config/index.html:/usr/share/jellyfin/web/index.html # ... other volumes
This gives the plugin the necessary permissions to inject JavaScript into the web interface.
-
After installing, navigate to Dashboard > Plugins > JavaScript Injector in the list --OR-- click on "JS Injector" in the dashboard sidebar
-
Click Add Script to create a new entry.
-
Give your script a descriptive name.
-
Enter your code in the JavaScript Code text area.
-
Use the Enabled checkbox to control whether the script is active.
-
Click Save.
-
Refresh your browser to see the changes take effect.
A great way to test if the plugin is working.
(function() {
'use strict';
const toast= `
alert('Yay!, Javascript injection worked!');
`;
const scriptElem = document.createElement('script');
scriptElem.textContent = toast;
document.head.appendChild(scriptElem);
})();
This script adds a banner to the top of the page for a specific user.
// Change this to the username you want to target
(function () {
const targetUsername = 'admin';
const flashingBannerCSS = `
@keyframes flashBanner {
0% { background-color: #ffeb3b; color: black; }
50% { background-color: #ff2111; color: white; }
100% { background-color: #ffeb3b; color: black; }
}
.skinHeader::before {
content: "⚠️ NOTICE: Special Banner for ${targetUsername} ⚠️";
display: block;
width: 100%;
text-align: center;
font-weight: bold;
font-size: 1.2rem;
padding: 0px;
animation: flashBanner 1s infinite;
position: relative;
z-index: 9999;
}
`;
function tryInjectBanner() {
const userButton = document.querySelector(".headerUserButton");
if (userButton && userButton.title.toLowerCase() === targetUsername.toLowerCase()) {
const styleElem = document.createElement('style');
styleElem.innerText = flashingBannerCSS;
document.head.appendChild(styleElem);
return true;
}
return false;
}
const interval = setInterval(() => {
if (tryInjectBanner()) clearInterval(interval);
}, 300);
})();Other Jellyfin plugins can programmatically register JavaScript snippets using the IJavaScriptRegistrationService interface. Here's an example of how to use it:
using System.Reflection;
using System.Runtime.Loader;
using Newtonsoft.Json.Linq;
public class YourPlugin : BasePlugin
{
public void RegisterYourScript()
{
try
{
// Find the JavaScript Injector assembly
Assembly? jsInjectorAssembly = AssemblyLoadContext.All
.SelectMany(x => x.Assemblies)
.FirstOrDefault(x => x.FullName?.Contains("Jellyfin.Plugin.JavaScriptInjector") ?? false);
if (jsInjectorAssembly != null)
{
// Get the PluginInterface type
Type? pluginInterfaceType = jsInjectorAssembly.GetType("Jellyfin.Plugin.JavaScriptInjector.PluginInterface");
if (pluginInterfaceType != null)
{
// Create the registration payload
var scriptRegistration = new JObject
{
{ "id", $"{Id}-my-script" }, // Unique ID for your script
{ "name", "My Custom Script" },
{ "script", @"
// Your JavaScript code here
console.log('Hello from my plugin!');
" },
{ "enabled", true },
{ "requiresAuthentication", false }, // Set to true if script should only run for logged-in users
{ "pluginId", Id.ToString() },
{ "pluginName", Name },
{ "pluginVersion", Version.ToString() }
};
// Register the script
var registerResult = pluginInterfaceType.GetMethod("RegisterScript")?.Invoke(null, new object[] { scriptRegistration });
if (registerResult is bool success && success)
{
_logger.LogInformation("Successfully registered JavaScript with JavaScript Injector plugin.");
}
else
{
_logger.LogWarning("Failed to register JavaScript with JavaScript Injector plugin. RegisterScript returned false.");
}
}
}
}
catch (Exception ex)
{
_logger?.LogError(ex, "Failed to register JavaScript with JavaScript Injector plugin.");
}
}
public void UnregisterYourScripts()
{
try
{
// Find the JavaScript Injector assembly
Assembly? jsInjectorAssembly = AssemblyLoadContext.All
.SelectMany(x => x.Assemblies)
.FirstOrDefault(x => x.FullName?.Contains("Jellyfin.Plugin.JavaScriptInjector") ?? false);
if (jsInjectorAssembly != null)
{
Type? pluginInterfaceType = jsInjectorAssembly.GetType("Jellyfin.Plugin.JavaScriptInjector.PluginInterface");
if (pluginInterfaceType != null)
{
var unregisterResult = pluginInterfaceType.GetMethod("UnregisterAllScriptsFromPlugin")?.Invoke(null, new object[] { Id.ToString() });
// or if you want to unregister a specific script
//pluginInterfaceType.GetMethod("UnregisterScript")?.Invoke(null, new object[] { $"{Id}-my-script" }); // -> returns bool, so adjust the result handling accordingly
if (unregisterResult is int removedCount)
{
_logger?.LogInformation("Successfully unregistered {Count} script(s) from JavaScript Injector plugin.", removedCount);
}
else
{
_logger?.LogWarning("Failed to unregister scripts from JavaScript Injector plugin. Method returned unexpected value.");
}
}
}
}
catch (Exception ex)
{
_logger?.LogError(ex, "Failed to unregister JavaScript scripts.");
}
}
}This plugin is a fork of and builds upon the original work of johnpc. Thanks to the original author for creating the foundation for this project.
Be careful when using any custom JavaScript, as it can potentially introduce security vulnerabilities or break the Jellyfin UI. Only use code from trusted sources or code that you have written and fully understand.
Made with 💜 for Jellyfin and the community
Checkout my other repos!
Jellyfin-Enhanced (javascript/plugin) • Jellyfin-Elsewhere (javascript) • Jellyfin-Tweaks (plugin) • Jellyfin-JavaScript-Injector (plugin) • Jellyfish (theme)