Skip to main content
Jellyfin’s plugin system allows you to extend the server with custom functionality, metadata providers, and integrations.

Plugin Architecture

Jellyfin plugins are .NET assemblies that implement the plugin interface and can:
  • Add metadata providers (movies, TV shows, music)
  • Implement custom authentication providers
  • Add new API endpoints
  • React to server events
  • Provide custom configuration pages
  • Extend media scanning and organization

Plugin Structure

Plugins are located in the plugins/ directory within the Jellyfin data folder and follow this structure:
plugins/
├── MyPlugin/
│   ├── MyPlugin.dll          # Main plugin assembly
│   ├── meta.json             # Plugin manifest
│   ├── configuration.xml     # Plugin configuration
│   └── thumb.jpg             # Plugin icon (optional)

Creating a Plugin

1

Create a New .NET Project

Create a new class library targeting .NET 9.0:
dotnet new classlib -n Jellyfin.Plugin.MyPlugin -f net9.0
cd Jellyfin.Plugin.MyPlugin
2

Add Jellyfin Dependencies

Add references to Jellyfin packages:
MyPlugin.csproj
<Project Sdk="Microsoft.NET.Sdk">
  <PropertyGroup>
    <TargetFramework>net9.0</TargetFramework>
    <AssemblyName>Jellyfin.Plugin.MyPlugin</AssemblyName>
    <GenerateDocumentationFile>true</GenerateDocumentationFile>
  </PropertyGroup>
  
  <ItemGroup>
    <PackageReference Include="Jellyfin.Controller" Version="10.*" />
    <PackageReference Include="Jellyfin.Model" Version="10.*" />
  </ItemGroup>
</Project>
Check the Jellyfin NuGet feed for the latest package versions.
3

Implement the Plugin Class

Create your main plugin class by inheriting from BasePlugin:
Plugin.cs
using System;
using MediaBrowser.Common.Configuration;
using MediaBrowser.Common.Plugins;
using MediaBrowser.Model.Plugins;
using MediaBrowser.Model.Serialization;

namespace Jellyfin.Plugin.MyPlugin
{
    public class Plugin : BasePlugin<PluginConfiguration>
    {
        public Plugin(
            IApplicationPaths applicationPaths,
            IXmlSerializer xmlSerializer)
            : base(applicationPaths, xmlSerializer)
        {
            Instance = this;
        }
        
        public static Plugin Instance { get; private set; }
        
        public override Guid Id => Guid.Parse("12345678-1234-1234-1234-123456789012");
        
        public override string Name => "My Plugin";
        
        public override string Description => "Does something awesome with Jellyfin";
    }
}
Generate a unique GUID for your plugin. Never reuse GUIDs from other plugins.
4

Create Plugin Configuration

Define your plugin’s configuration class:
PluginConfiguration.cs
using MediaBrowser.Model.Plugins;

namespace Jellyfin.Plugin.MyPlugin
{
    public class PluginConfiguration : BasePluginConfiguration
    {
        public string ApiKey { get; set; } = string.Empty;
        
        public bool EnableFeature { get; set; } = true;
        
        public int MaxResults { get; set; } = 10;
    }
}
5

Build Your Plugin

dotnet build --configuration Release

Plugin Interface Reference

All plugins must implement the IPlugin interface defined in MediaBrowser.Common/Plugins/IPlugin.cs:
IPlugin Interface
public interface IPlugin
{
    string Name { get; }                    // Plugin display name
    string Description { get; }              // Plugin description
    Guid Id { get; }                        // Unique plugin identifier
    Version Version { get; }                 // Plugin version
    string AssemblyFilePath { get; }        // Path to plugin DLL
    bool CanUninstall { get; }              // Whether plugin can be removed
    string DataFolderPath { get; }          // Plugin data storage path
    
    PluginInfo GetPluginInfo();             // Returns plugin metadata
    void OnUninstalling();                   // Called before uninstall
}

BasePlugin Class

The BasePlugin<TConfigurationType> class (from MediaBrowser.Common/Plugins/BasePlugin.cs) provides:
  • Configuration Management: Automatic XML serialization/deserialization
  • Data Folder Access: Isolated storage for plugin data
  • Lifecycle Hooks: OnUninstalling() method
  • Plugin Info: Automatic PluginInfo generation
Inherit from BasePlugin<TConfigurationType> rather than implementing IPlugin directly.

Example: TMDb Metadata Plugin

Here’s how the built-in TMDb plugin is structured (see MediaBrowser.Providers/Plugins/Tmdb/Plugin.cs):
TMDb Plugin Example
using System;
using System.Collections.Generic;
using MediaBrowser.Common.Configuration;
using MediaBrowser.Common.Plugins;
using MediaBrowser.Model.Plugins;
using MediaBrowser.Model.Serialization;

namespace MediaBrowser.Providers.Plugins.Tmdb
{
    public class Plugin : BasePlugin<PluginConfiguration>, IHasWebPages
    {
        public Plugin(
            IApplicationPaths applicationPaths,
            IXmlSerializer xmlSerializer)
            : base(applicationPaths, xmlSerializer)
        {
            Instance = this;
        }
        
        public static Plugin Instance { get; private set; }
        
        public override Guid Id => new Guid("b8715ed1-6c47-4528-9ad3-f72deb539cd4");
        
        public override string Name => "TMDb";
        
        public override string Description => "Get metadata for movies and other video content from TheMovieDb.";
        
        public override string ConfigurationFileName => "Jellyfin.Plugin.Tmdb.xml";
        
        // Provide configuration page
        public IEnumerable<PluginPageInfo> GetPages()
        {
            yield return new PluginPageInfo
            {
                Name = Name,
                EmbeddedResourcePath = GetType().Namespace + ".Configuration.config.html"
            };
        }
    }
}

Adding Configuration Pages

Implement IHasWebPages to provide a web-based configuration UI:
1

Implement IHasWebPages

public class Plugin : BasePlugin<PluginConfiguration>, IHasWebPages
{
    public IEnumerable<PluginPageInfo> GetPages()
    {
        yield return new PluginPageInfo
        {
            Name = "My Plugin Config",
            EmbeddedResourcePath = GetType().Namespace + ".Configuration.config.html"
        };
    }
}
2

Create Configuration HTML

Add an HTML file as an embedded resource:
Configuration/config.html
<!DOCTYPE html>
<html>
<head>
    <title>My Plugin Configuration</title>
</head>
<body>
    <div data-role="page" class="page type-interior pluginConfigurationPage">
        <div data-role="content">
            <form class="pluginConfigurationForm">
                <label for="txtApiKey">API Key:</label>
                <input type="text" id="txtApiKey" name="ApiKey" />
                
                <label>
                    <input type="checkbox" id="chkEnableFeature" name="EnableFeature" />
                    Enable Feature
                </label>
                
                <button type="submit">Save</button>
            </form>
        </div>
    </div>
    
    <script type="text/javascript">
        $('.pluginConfigurationForm').on('submit', function(e) {
            e.preventDefault();
            ApiClient.getPluginConfiguration(PluginConfig.pluginId).then(function(config) {
                config.ApiKey = $('#txtApiKey').val();
                config.EnableFeature = $('#chkEnableFeature').is(':checked');
                ApiClient.updatePluginConfiguration(PluginConfig.pluginId, config);
            });
        });
    </script>
</body>
</html>
3

Embed the Resource

Update your .csproj file:
<ItemGroup>
  <EmbeddedResource Include="Configuration\\config.html" />
</ItemGroup>

Plugin API Management

The Plugin API is managed through the PluginsController (see Jellyfin.Api/Controllers/PluginsController.cs):

Available Endpoints

GET /Plugins
Returns all installed plugins sorted by name.

Advanced Plugin Features

Implementing Metadata Providers

Create custom metadata providers for movies, TV shows, or music:
using MediaBrowser.Controller.Entities.Movies;
using MediaBrowser.Controller.Providers;
using MediaBrowser.Model.Entities;

public class MyMetadataProvider : IRemoteMetadataProvider<Movie, MovieInfo>
{
    public string Name => "My Metadata Provider";
    
    public async Task<MetadataResult<Movie>> GetMetadata(
        MovieInfo info,
        CancellationToken cancellationToken)
    {
        var result = new MetadataResult<Movie>();
        
        // Fetch metadata from your source
        result.Item = new Movie
        {
            Name = "Movie Title",
            Overview = "Movie description",
            ProductionYear = 2024
        };
        
        result.HasMetadata = true;
        return result;
    }
    
    public Task<IEnumerable<RemoteSearchResult>> GetSearchResults(
        MovieInfo searchInfo,
        CancellationToken cancellationToken)
    {
        // Return search results
        return Task.FromResult(Enumerable.Empty<RemoteSearchResult>());
    }
}

Reacting to Server Events

Subscribe to server events using dependency injection:
using MediaBrowser.Controller.Library;
using MediaBrowser.Controller.Plugins;

public class MyServerEntryPoint : IServerEntryPoint
{
    private readonly ILibraryManager _libraryManager;
    
    public MyServerEntryPoint(ILibraryManager libraryManager)
    {
        _libraryManager = libraryManager;
    }
    
    public Task RunAsync()
    {
        // Subscribe to events
        _libraryManager.ItemAdded += OnItemAdded;
        _libraryManager.ItemUpdated += OnItemUpdated;
        return Task.CompletedTask;
    }
    
    private void OnItemAdded(object sender, ItemChangeEventArgs e)
    {
        // Handle new items
    }
    
    private void OnItemUpdated(object sender, ItemChangeEventArgs e)
    {
        // Handle updated items
    }
    
    public void Dispose()
    {
        _libraryManager.ItemAdded -= OnItemAdded;
        _libraryManager.ItemUpdated -= OnItemUpdated;
    }
}

Adding Scheduled Tasks

using MediaBrowser.Controller.Library;
using MediaBrowser.Model.Tasks;

public class MyScheduledTask : IScheduledTask
{
    public string Name => "My Scheduled Task";
    
    public string Description => "Runs periodically to do something";
    
    public string Category => "Maintenance";
    
    public string Key => "MyPluginScheduledTask";
    
    public async Task ExecuteAsync(IProgress<double> progress, CancellationToken cancellationToken)
    {
        // Perform task
        progress.Report(50);
        await Task.Delay(1000, cancellationToken);
        progress.Report(100);
    }
    
    public IEnumerable<TaskTriggerInfo> GetDefaultTriggers()
    {
        return new[]
        {
            new TaskTriggerInfo
            {
                Type = TaskTriggerInfo.TriggerDaily,
                TimeOfDayTicks = TimeSpan.FromHours(2).Ticks
            }
        };
    }
}

Installing Your Plugin

  1. Build your plugin in Release mode:
    dotnet build --configuration Release
    
  2. Copy the DLL to the plugins folder:
    cp bin/Release/net9.0/Jellyfin.Plugin.MyPlugin.dll \
       /path/to/jellyfin/data/plugins/MyPlugin/
    
  3. Restart Jellyfin Server

Debugging Plugins

1

Build with Debug Symbols

dotnet build --configuration Debug
2

Attach Debugger

In Visual Studio or VS Code, attach to the running Jellyfin process.VS Code launch.json:
{
  "name": "Attach to Jellyfin",
  "type": "coreclr",
  "request": "attach",
  "processName": "jellyfin"
}
3

Set Breakpoints

Set breakpoints in your plugin code and trigger the functionality.
Enable verbose logging in Jellyfin to see plugin initialization messages and errors.

Plugin Best Practices

Handle Errors Gracefully

Always catch and log exceptions. Don’t crash the server with unhandled exceptions.

Use Dependency Injection

Request dependencies through constructor injection rather than creating instances directly.

Implement IDisposable

Clean up resources (event handlers, file handles, connections) when your plugin is unloaded.

Version Your Plugin

Use semantic versioning and test compatibility with different Jellyfin versions.

Document Configuration

Provide clear descriptions for all configuration options in your UI.

Respect Cancellation Tokens

Honor CancellationToken parameters to allow graceful task cancellation.

Plugin Examples

Study these built-in plugins in the Jellyfin source:
  • TMDb Plugin: MediaBrowser.Providers/Plugins/Tmdb/ - Metadata provider
  • MusicBrainz Plugin: MediaBrowser.Providers/Plugins/MusicBrainz/ - Music metadata
  • Studio Images Plugin: MediaBrowser.Providers/Plugins/StudioImages/ - Image provider
  • AudioDB Plugin: MediaBrowser.Providers/Plugins/AudioDb/ - Audio metadata

Testing Your Plugin

Create unit tests for your plugin:
using Xunit;

public class PluginTests
{
    [Fact]
    public void Plugin_HasCorrectId()
    {
        var plugin = new Plugin(null, null);
        Assert.NotEqual(Guid.Empty, plugin.Id);
    }
    
    [Fact]
    public void Configuration_DefaultValues()
    {
        var config = new PluginConfiguration();
        Assert.True(config.EnableFeature);
        Assert.Equal(10, config.MaxResults);
    }
}

Next Steps

API Overview

Learn about the Jellyfin API your plugin can use

Contributing Guide

Submit your plugin to the official repository

Building from Source

Set up a development environment

Plugin Repository

Browse existing plugins and submission guidelines