Extend Jellyfin Server with custom plugins for additional functionality
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.
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; } }}
Jellyfin supports various plugin types for different functionality:
Metadata Providers
Authentication Providers
Notification Services
Scheduled Tasks
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 }; }}
Implement custom authentication:
public class CustomAuthProvider : IAuthenticationProvider{ public string Name => "Custom Authentication"; public bool IsEnabled => true; public async Task<ProviderAuthenticationResult> Authenticate( string username, string password) { // Validate against external system var isValid = await ValidateCredentialsAsync(username, password); if (!isValid) { throw new AuthenticationException( "Invalid username or password"); } return new ProviderAuthenticationResult { Username = username }; } public Task ChangePassword(User user, string newPassword) { // Update password in external system return UpdatePasswordAsync(user.Username, newPassword); }}
Send notifications to external services:
public class CustomNotifier : INotificationService{ public string Name => "Custom Notifier"; public async Task SendNotification( UserNotification request, CancellationToken cancellationToken) { // Send notification to external service await SendToExternalServiceAsync( request.User.Username, request.Name, request.Description); }}
Run background tasks on a schedule:
public class MyScheduledTask : IScheduledTask{ public string Name => "My Scheduled Task"; public string Key => "MyScheduledTask"; public string Description => "Performs custom maintenance"; public string Category => "Maintenance"; public Task ExecuteAsync( IProgress<double> progress, CancellationToken cancellationToken) { // Perform task progress.Report(0); // Do work... progress.Report(100); return Task.CompletedTask; } public IEnumerable<TaskTriggerInfo> GetDefaultTriggers() { return new[] { new TaskTriggerInfo { Type = TaskTriggerInfo.TriggerDaily, TimeOfDayTicks = TimeSpan.FromHours(3).Ticks } }; }}
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); }}
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); }}