Automatically delete media according to configurable rules.
C#
262
115 commits
updated Sep 26, 2026
Automatically delete media according to configurable rules. Works with any media types.
Add repository with my plugins from jellyfin-plugin-repo.
Media Cleaner is configured via the rules.
Rules are split into cleanup rules and protection rules.
Each rule has:
For episode cleanup rules, the deletion scope controls whether Media Cleaner deletes matching episodes individually, complete seasons, complete series, or complete ended series. Episode and season scopes can keep the first or latest item in the entire series as an exception, even when that item does not match the rule. Individual-episode cleanup can instead keep the episode most recently watched by the users included in that rule. Cleanup is blocked when the configured exception cannot be identified safely.
Played cleanup can require playback by at least one user, the most recent play by any user, or every user. Not-played cleanup can ignore older playback history so old watches do not keep new imports forever.
Tag filters only affect rule matching. Changing a tag in a rule does not rename tags already stored on Jellyfin items; use Advanced for library tag maintenance.
Leaving Soon is optional. When enabled, Media Cleaner adds cleanup candidates to a Jellyfin collection before deleting them. Deletion waits for the configured notice period, which can be overridden by individual cleanup rules. If several rules match, the longest notice period is used. An item that no longer matches any cleanup rule is removed from the collection.
Users can select Keep until watched from an item's menu in Jellyfin Web. The item remains protected until that user marks it as played or removes the protection. Starting or partially watching it is not enough. For a season or series, Media Cleaner protects its currently visible, unplayed episodes; episodes added later are not included automatically.
Favorites and tags continue to follow the configured cleanup and protection rules. Administrators can review and remove active personal protections on the Leaving Soon keeps tab. Disabling Leaving Soon removes the warnings but keeps existing personal protections.
The collection is available through Jellyfin's normal Collections view. Keep until watched is added only to the Jellyfin Web installation hosted by the server; separate web clients can still open the collection but do not receive the extra menu action. Media Cleaner injects that action through JavaScript Injector when available, then File Transformation, otherwise through its built-in startup filter, and modifies index.html on disk only as a final fallback.
The Advanced tab contains tools and safety switches that should not be hidden inside ordinary rules:
LastPlayedDate is earlier than DateCreated / "Date Added".The Troubleshooting tab runs a dry-run cleanup report and opens it in a formatted viewer. It shows the plugin configuration, final delete decisions, planned deletion operations, audit entries, outcome summaries, rule-level decisions and item-level decisions. Use it before enabling risky rules or when a rule does not match the items you expected.
Saving Media Cleaner settings does not delete anything. Deletion happens only when Jellyfin runs the Media Cleaner cleanup scheduled task, either automatically or manually.
Saving Leaving Soon settings refreshes its collection but does not delete media. The Media Cleaner Leaving Soon refresh task also updates the collection every six hours by default.
The cleanup task runs once per day by default. Its schedule, manual runs and enabled state are controlled from Jellyfin's Scheduled Tasks page.
On each task run, Media Cleaner loads the current rules, scans the Jellyfin library, builds a cleanup plan, applies protection rules and cascade safety checks, then executes the planned delete operations. Media currently being watched is always protected, including from season and series cascades. If no cleanup rule matches, no item is deleted.
The Troubleshooting report uses the same planning path in dry-run mode. It shows what would be deleted by a real scheduled-task run, but the report itself does not delete or modify media.
Jellyfin's "Date added behavior for new content" setting affects rules that use "played" or "not played" state, because Media Cleaner compares Jellyfin's DateCreated / "Date Added" value with the user's LastPlayedDate.
There is no single correct setting for every Radarr/Sonarr/download-client setup:
LastPlayedDate. Media Cleaner treats this as ambiguous by default: played cleanup will not use that playback, and not-played cleanup will not delete the item solely because of the date conflict.Changing Jellyfin's setting does not usually rewrite existing item dates.
If a rule behaves unexpectedly, run Media Cleaner's troubleshooting report and look for entries where LastPlayedDate is before Jellyfin "Date Added". Those items usually need Jellyfin metadata correction, a different Jellyfin date-added mode for future imports, or an external date-preservation workaround.
If a rule is configured to ignore older playback history, playback before "Date Added" is only treated as ambiguous while it is still inside that history window.
Media Cleaner's advanced "Count playback before Date Added" setting changes this safety behavior. It counts playback even when LastPlayedDate is earlier than "Date Added". This can help with upgraded or re-imported media where Jellyfin reset the added date. Enable it only if you accept the tradeoff: newly re-downloaded media can be deleted based on old watch history.
Media Cleaner relies on Jellyfin playback dates when evaluating rules based on played media. Some Jellyfin clients or imported playback data can mark an item as played without setting a LastPlayedDate.
When this happens, Media Cleaner cannot know when the item was actually watched. The plugin intentionally does not invent a playback date, because doing so could make cleanup decisions unsafe or misleading.
If played items are not deleted as expected, check the item/user playback data in Jellyfin first. Fix the source client, import process, or use an external repair/sync tool that writes correct Jellyfin playback dates before relying on played-date cleanup rules. One reported workaround is jellyfin-watch-updater, which can set LastPlayedDate by marking items as played through Jellyfin's API.
Define JellyfinHome environment variable pointing to Jellyfin distribution to be able to run debug configuration.
Builds are selected through JellyfinProfile. Server profiles are mapped to ABI build profiles in eng/jellyfin-profiles.json, so compatible server versions share release artifacts. Directory.JellyfinProfiles.props is generated from that JSON and imported by MSBuild.
dotnet build MediaCleaner.sln -p:JellyfinProfile=10.10.7
dotnet build MediaCleaner.sln -p:JellyfinProfile=10.11.0
dotnet build MediaCleaner.sln -p:JellyfinProfile=10.11.3
dotnet build MediaCleaner.sln -p:JellyfinProfile=10.11.11
dotnet build MediaCleaner.sln -p:JellyfinProfile=12.0
dotnet build MediaCleaner.sln -p:JellyfinProfile=12.1
Use the packaging helper to generate ABI build artifacts and build.yaml files under artifacts/<build-profile>:
.\eng\build-plugin.ps1 -Configuration Release
When Jellyfin changes ABI, add or update a build profile in eng/jellyfin-profiles.json with the package version, target ABI, target framework, versionPatchOffset and any compile constants needed by MediaCleaner/Compatibility/JellyfinCompatibility.cs. Map server versions to build profiles in the same file, then run .\eng\generate-jellyfin-props.ps1. Keep direct Jellyfin API changes in the compatibility layer first; the task, filters and controllers should call that layer instead of branching on server versions directly.
To bump the plugin release version, update baseVersion and regenerate MSBuild profile properties:
.\eng\bump-version.ps1
.\eng\bump-version.ps1 -Version 2.25.0
You're welcome to use "generative AI" coding tools when contributing, however:
C#
74.3%
JavaScript
16.2%
HTML
6.4%
PowerShell
3.2%
Automatically delete media according to configurable rules.
C#
262
115 commits
updated Sep 26, 2026
Automatically delete media according to configurable rules. Works with any media types.
Add repository with my plugins from jellyfin-plugin-repo.
Media Cleaner is configured via the rules.
Rules are split into cleanup rules and protection rules.
Each rule has:
For episode cleanup rules, the deletion scope controls whether Media Cleaner deletes matching episodes individually, complete seasons, complete series, or complete ended series. Episode and season scopes can keep the first or latest item in the entire series as an exception, even when that item does not match the rule. Individual-episode cleanup can instead keep the episode most recently watched by the users included in that rule. Cleanup is blocked when the configured exception cannot be identified safely.
Played cleanup can require playback by at least one user, the most recent play by any user, or every user. Not-played cleanup can ignore older playback history so old watches do not keep new imports forever.
Tag filters only affect rule matching. Changing a tag in a rule does not rename tags already stored on Jellyfin items; use Advanced for library tag maintenance.
Leaving Soon is optional. When enabled, Media Cleaner adds cleanup candidates to a Jellyfin collection before deleting them. Deletion waits for the configured notice period, which can be overridden by individual cleanup rules. If several rules match, the longest notice period is used. An item that no longer matches any cleanup rule is removed from the collection.
Users can select Keep until watched from an item's menu in Jellyfin Web. The item remains protected until that user marks it as played or removes the protection. Starting or partially watching it is not enough. For a season or series, Media Cleaner protects its currently visible, unplayed episodes; episodes added later are not included automatically.
Favorites and tags continue to follow the configured cleanup and protection rules. Administrators can review and remove active personal protections on the Leaving Soon keeps tab. Disabling Leaving Soon removes the warnings but keeps existing personal protections.
The collection is available through Jellyfin's normal Collections view. Keep until watched is added only to the Jellyfin Web installation hosted by the server; separate web clients can still open the collection but do not receive the extra menu action. Media Cleaner injects that action through JavaScript Injector when available, then File Transformation, otherwise through its built-in startup filter, and modifies index.html on disk only as a final fallback.
The Advanced tab contains tools and safety switches that should not be hidden inside ordinary rules:
LastPlayedDate is earlier than DateCreated / "Date Added".The Troubleshooting tab runs a dry-run cleanup report and opens it in a formatted viewer. It shows the plugin configuration, final delete decisions, planned deletion operations, audit entries, outcome summaries, rule-level decisions and item-level decisions. Use it before enabling risky rules or when a rule does not match the items you expected.
Saving Media Cleaner settings does not delete anything. Deletion happens only when Jellyfin runs the Media Cleaner cleanup scheduled task, either automatically or manually.
Saving Leaving Soon settings refreshes its collection but does not delete media. The Media Cleaner Leaving Soon refresh task also updates the collection every six hours by default.
The cleanup task runs once per day by default. Its schedule, manual runs and enabled state are controlled from Jellyfin's Scheduled Tasks page.
On each task run, Media Cleaner loads the current rules, scans the Jellyfin library, builds a cleanup plan, applies protection rules and cascade safety checks, then executes the planned delete operations. Media currently being watched is always protected, including from season and series cascades. If no cleanup rule matches, no item is deleted.
The Troubleshooting report uses the same planning path in dry-run mode. It shows what would be deleted by a real scheduled-task run, but the report itself does not delete or modify media.
Jellyfin's "Date added behavior for new content" setting affects rules that use "played" or "not played" state, because Media Cleaner compares Jellyfin's DateCreated / "Date Added" value with the user's LastPlayedDate.
There is no single correct setting for every Radarr/Sonarr/download-client setup:
LastPlayedDate. Media Cleaner treats this as ambiguous by default: played cleanup will not use that playback, and not-played cleanup will not delete the item solely because of the date conflict.Changing Jellyfin's setting does not usually rewrite existing item dates.
If a rule behaves unexpectedly, run Media Cleaner's troubleshooting report and look for entries where LastPlayedDate is before Jellyfin "Date Added". Those items usually need Jellyfin metadata correction, a different Jellyfin date-added mode for future imports, or an external date-preservation workaround.
If a rule is configured to ignore older playback history, playback before "Date Added" is only treated as ambiguous while it is still inside that history window.
Media Cleaner's advanced "Count playback before Date Added" setting changes this safety behavior. It counts playback even when LastPlayedDate is earlier than "Date Added". This can help with upgraded or re-imported media where Jellyfin reset the added date. Enable it only if you accept the tradeoff: newly re-downloaded media can be deleted based on old watch history.
Media Cleaner relies on Jellyfin playback dates when evaluating rules based on played media. Some Jellyfin clients or imported playback data can mark an item as played without setting a LastPlayedDate.
When this happens, Media Cleaner cannot know when the item was actually watched. The plugin intentionally does not invent a playback date, because doing so could make cleanup decisions unsafe or misleading.
If played items are not deleted as expected, check the item/user playback data in Jellyfin first. Fix the source client, import process, or use an external repair/sync tool that writes correct Jellyfin playback dates before relying on played-date cleanup rules. One reported workaround is jellyfin-watch-updater, which can set LastPlayedDate by marking items as played through Jellyfin's API.
Define JellyfinHome environment variable pointing to Jellyfin distribution to be able to run debug configuration.
Builds are selected through JellyfinProfile. Server profiles are mapped to ABI build profiles in eng/jellyfin-profiles.json, so compatible server versions share release artifacts. Directory.JellyfinProfiles.props is generated from that JSON and imported by MSBuild.
dotnet build MediaCleaner.sln -p:JellyfinProfile=10.10.7
dotnet build MediaCleaner.sln -p:JellyfinProfile=10.11.0
dotnet build MediaCleaner.sln -p:JellyfinProfile=10.11.3
dotnet build MediaCleaner.sln -p:JellyfinProfile=10.11.11
dotnet build MediaCleaner.sln -p:JellyfinProfile=12.0
dotnet build MediaCleaner.sln -p:JellyfinProfile=12.1
Use the packaging helper to generate ABI build artifacts and build.yaml files under artifacts/<build-profile>:
.\eng\build-plugin.ps1 -Configuration Release
When Jellyfin changes ABI, add or update a build profile in eng/jellyfin-profiles.json with the package version, target ABI, target framework, versionPatchOffset and any compile constants needed by MediaCleaner/Compatibility/JellyfinCompatibility.cs. Map server versions to build profiles in the same file, then run .\eng\generate-jellyfin-props.ps1. Keep direct Jellyfin API changes in the compatibility layer first; the task, filters and controllers should call that layer instead of branching on server versions directly.
To bump the plugin release version, update baseVersion and regenerate MSBuild profile properties:
.\eng\bump-version.ps1
.\eng\bump-version.ps1 -Version 2.25.0
You're welcome to use "generative AI" coding tools when contributing, however:
C#
74.3%
JavaScript
16.2%
HTML
6.4%
PowerShell
3.2%