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; }}
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" }; } }}
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" }; }}
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>()); }}
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 } }; }}
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); }}