Skip to main content
Jellyfin’s plugin system allows developers to extend the server’s functionality without modifying the core codebase. Plugins can add new metadata providers, authentication methods, notification services, and more.

Plugin Architecture

Plugins in Jellyfin are .NET assemblies that implement specific interfaces and are loaded at server startup.

Plugin Manager

The PluginManager discovers, loads, and manages plugins:
Emby.Server.Implementations/Plugins/PluginManager.cs
public sealed class PluginManager : IPluginManager, IDisposable
{
    private const string MetafileName = "meta.json";
    private readonly string _pluginsPath;
    private readonly Version _appVersion;
    private readonly List<LocalPlugin> _plugins;
    private readonly List<AssemblyLoadContext> _assemblyLoadContexts;
    
    public IReadOnlyList<LocalPlugin> Plugins => _plugins;
    
    public PluginManager(
        ILogger<PluginManager> logger,
        IServerApplicationHost appHost,
        ServerConfiguration config,
        string pluginsPath,
        Version appVersion)
    {
        _pluginsPath = pluginsPath;
        _appVersion = appVersion;
        _plugins = Directory.Exists(_pluginsPath) 
            ? DiscoverPlugins().ToList() 
            : new List<LocalPlugin>();
    }
}
Plugins are loaded in isolated AssemblyLoadContext instances to prevent version conflicts and enable unloading.

Plugin Discovery

Plugins are discovered by scanning the plugins directory:
1

Directory Scan

The PluginManager scans the configured plugins path (typically /config/plugins).
2

Metadata Loading

Each plugin directory should contain a meta.json file:
{
  "guid": "a4df60c5-6ab4-412a-8f79-2cab93fb2bc5",
  "name": "My Custom Plugin",
  "description": "Adds custom functionality to Jellyfin",
  "version": "1.0.0",
  "status": "Active",
  "autoUpdate": true,
  "targetAbi": "10.8.0.0"
}
3

Version Validation

Plugin compatibility is checked against the server version:
private void UpdatePluginSupersededStatus(LocalPlugin plugin)
{
    if (plugin.Manifest.TargetAbi != null && 
        !IsCompatible(plugin.Manifest.TargetAbi, _appVersion))
    {
        plugin.Manifest.Status = PluginStatus.NotSupported;
    }
}
4

Assembly Loading

Compatible plugins are loaded into the application:
public IEnumerable<Assembly> LoadAssemblies()
{
    foreach (var plugin in _plugins)
    {
        if (plugin.IsEnabledAndSupported == false)
        {
            _logger.LogInformation(
                "Skipping disabled plugin {Version} of {Name}",
                plugin.Version, plugin.Name);
            continue;
        }
        
        var assemblyLoadContext = new PluginLoadContext(plugin.Path);
        _assemblyLoadContexts.Add(assemblyLoadContext);
        
        foreach (var file in plugin.DllFiles)
        {
            assemblies.Add(
                assemblyLoadContext.LoadFromAssemblyPath(file));
        }
    }
}

Creating a Plugin

Basic Plugin Structure

All plugins must inherit from BasePlugin:
MediaBrowser.Common/Plugins/BasePlugin.cs
public abstract class BasePlugin : IPlugin, IPluginAssembly
{
    public abstract string Name { get; }
    public virtual string Description => string.Empty;
    public virtual Guid Id { get; private set; }
    public Version Version { get; private set; }
    public string AssemblyFilePath { get; private set; }
    public string DataFolderPath { get; private set; }
    
    public bool CanUninstall => !Path.GetDirectoryName(AssemblyFilePath)
        .Equals(Path.GetDirectoryName(
            Assembly.GetExecutingAssembly().Location), 
            StringComparison.Ordinal);
    
    public virtual PluginInfo GetPluginInfo()
    {
        return new PluginInfo(
            Name,
            Version,
            Description,
            Id,
            CanUninstall);
    }
    
    public virtual void OnUninstalling() { }
}

Plugin Interfaces

MediaBrowser.Common/Plugins/IPlugin.cs
public interface IPlugin
{
    string Name { get; }
    string Description { get; }
    Guid Id { get; }
    Version Version { get; }
    string AssemblyFilePath { get; }
    bool CanUninstall { get; }
    string DataFolderPath { get; }
    
    PluginInfo GetPluginInfo();
    void OnUninstalling();
}

Example Plugin Implementation

using MediaBrowser.Common.Plugins;
using MediaBrowser.Model.Plugins;

namespace MyCustomPlugin
{
    public class Plugin : BasePlugin
    {
        public override string Name => "My Custom Plugin";
        
        public override string Description => 
            "Provides custom functionality for Jellyfin";
        
        public override Guid Id => 
            Guid.Parse("a4df60c5-6ab4-412a-8f79-2cab93fb2bc5");
        
        public Plugin(
            IApplicationPaths applicationPaths,
            IXmlSerializer xmlSerializer)
        {
            Instance = this;
        }
        
        public static Plugin? Instance { get; private set; }
    }
}

Service Registration

Plugins can register services with the dependency injection container:
MediaBrowser.Controller/Plugins/IPluginServiceRegistrator.cs
public interface IPluginServiceRegistrator
{
    void RegisterServices(
        IServiceCollection serviceCollection, 
        IServerApplicationHost applicationHost);
}

Example Service Registration

using MediaBrowser.Controller.Plugins;
using Microsoft.Extensions.DependencyInjection;

namespace MyCustomPlugin
{
    public class PluginServiceRegistrator : IPluginServiceRegistrator
    {
        public void RegisterServices(
            IServiceCollection serviceCollection,
            IServerApplicationHost applicationHost)
        {
            // Register custom services
            serviceCollection.AddSingleton<IMyCustomService, MyCustomService>();
            serviceCollection.AddScoped<IMyRequestHandler, MyRequestHandler>();
            
            // Register hosted services (background tasks)
            serviceCollection.AddHostedService<MyBackgroundService>();
        }
    }
}
Service registration happens before the plugin assemblies are fully loaded, so you can inject services into other plugin components.

Plugin Types

Jellyfin supports various plugin types for different functionality:
Fetch metadata from external sources:
public class MyMetadataProvider : IRemoteMetadataProvider<Movie, MovieInfo>
{
    public string Name => "My Metadata Provider";
    
    public async Task<IEnumerable<RemoteSearchResult>> GetSearchResults(
        MovieInfo searchInfo, 
        CancellationToken cancellationToken)
    {
        // Search for movies
        var results = await SearchMoviesAsync(searchInfo.Name);
        return results;
    }
    
    public async Task<MetadataResult<Movie>> GetMetadata(
        MovieInfo info, 
        CancellationToken cancellationToken)
    {
        // Fetch full metadata
        var metadata = await FetchMovieMetadataAsync(info.ProviderIds);
        return new MetadataResult<Movie>
        {
            Item = metadata,
            HasMetadata = true
        };
    }
}

Plugin Configuration

Plugins can provide configuration UI and persist settings:

Configuration Class

using MediaBrowser.Model.Plugins;

public class PluginConfiguration : BasePluginConfiguration
{
    public string ApiUrl { get; set; } = "https://api.example.com";
    public string ApiKey { get; set; } = string.Empty;
    public int CacheDuration { get; set; } = 3600;
    public bool EnableDebugLogging { get; set; } = false;
    public List<string> AllowedUsers { get; set; } = new();
}

Configuration Storage

public class Plugin : BasePlugin<PluginConfiguration>
{
    public Plugin(
        IApplicationPaths applicationPaths,
        IXmlSerializer xmlSerializer)
        : base(applicationPaths, xmlSerializer)
    {
        Instance = this;
    }
    
    // Access configuration
    public string GetApiKey() => Configuration.ApiKey;
    
    // Update configuration
    public void UpdateConfiguration(PluginConfiguration config)
    {
        Configuration = config;
        SaveConfiguration();
    }
}
Plugin configurations are automatically serialized to XML and stored in the plugin’s data folder.

Plugin Lifecycle

1

Discovery

Plugins are discovered when the PluginManager scans the plugins directory during server startup.
2

Loading

Compatible and enabled plugins are loaded into AssemblyLoadContext instances.
3

Service Registration

Plugins implementing IPluginServiceRegistrator register their services with the DI container.
4

Initialization

Plugin constructors are called, and singletons are instantiated.
5

Runtime

Plugins respond to events and provide functionality as requested by the server.
6

Uninstallation

When uninstalling, OnUninstalling() is called for cleanup:
public override void OnUninstalling()
{
    // Clean up resources
    _httpClient?.Dispose();
    
    // Remove cached data
    if (Directory.Exists(DataFolderPath))
    {
        Directory.Delete(DataFolderPath, recursive: true);
    }
}

Plugin Repository

Official plugins are distributed through the Jellyfin plugin repository:
manifest.json
{
  "guid": "a4df60c5-6ab4-412a-8f79-2cab93fb2bc5",
  "name": "My Custom Plugin",
  "description": "Provides custom functionality",
  "overview": "Extended description of the plugin",
  "owner": "jellyfin",
  "category": "Metadata",
  "versions": [
    {
      "version": "1.0.0",
      "changelog": "Initial release",
      "targetAbi": "10.8.0.0",
      "sourceUrl": "https://repo.jellyfin.org/releases/plugin/my-plugin/my-plugin_1.0.0.zip",
      "checksum": "sha256:...",
      "timestamp": "2023-01-15T00:00:00Z"
    }
  ]
}

Official Plugins

Jellyfin provides several official plugins:

TMDb

Metadata provider for movies and TV shows from The Movie Database

LDAP Authentication

Authenticate users against LDAP/Active Directory

Trakt

Sync playback progress and ratings with Trakt.tv

AniDB

Anime metadata from AniDB

Bookshelf

E-book and audiobook management

Webhook

Send webhooks on server events

Reports

Generate usage and library reports

Slack Notifications

Send notifications to Slack

Plugin Development Best Practices

  • Use semantic versioning (MAJOR.MINOR.PATCH)
  • Specify targetAbi for compatibility checking
  • Document breaking changes in changelogs
  • Test against multiple Jellyfin versions
Plugins run in the same process as Jellyfin Server. Bugs or crashes in plugins can affect server stability.

Testing Plugins

using Xunit;
using Moq;

public class PluginTests
{
    [Fact]
    public async Task GetMetadata_ValidMovie_ReturnsMetadata()
    {
        // Arrange
        var provider = new MyMetadataProvider();
        var movieInfo = new MovieInfo { Name = "Test Movie" };
        
        // Act
        var result = await provider.GetMetadata(
            movieInfo, 
            CancellationToken.None);
        
        // Assert
        Assert.True(result.HasMetadata);
        Assert.NotNull(result.Item);
        Assert.Equal("Test Movie", result.Item.Name);
    }
}

Next Steps

Architecture

Understand how plugins fit into Jellyfin

Media Libraries

Learn about library management

Users & Authentication

Extend authentication with plugins

API Reference

Build plugins using the API