Intelligent media access control for Jellyfin
v3.4.0.0 is an intro-only build for Jellyfin 12. Only the per-policy intro provider is registered; media source filtering (and the
/QualityGateAPI) is not active until it has been re-validated against the Jellyfin 12 ABI. Policies and user assignments in the admin page keep working and drive intro selection. For version filtering on Jellyfin 12, use separate libraries per quality tier with Jellyfin's built-in Library Access settings.
- Filename Regex Patterns -- Match against filenames with regex for Jellyfin multi-version setups
- Per-User Assignments -- Assign different policies to different users
- Web Configuration -- Easy-to-use admin interface in Jellyfin dashboard
- Multi-Version Support -- Seamlessly filter available media versions per user
- Custom Intros -- Optional intro video per policy (e.g. a "lite" branding for restricted users)
- Dangling Symlink Protection -- Sources whose files don't exist on disk are automatically hidden
- Detailed Logging -- Full audit trail of access decisions
This plugin is designed for Jellyfin's multi-version naming convention, where multiple quality versions of the same movie live together:
movies/Movie (2021)/Movie (2021) - 2160p.mkv
movies/Movie (2021)/Movie (2021) - 1080p.mkv
movies/Movie (2021)/Movie (2021) - 720p.mkv
| Scenario | Solution |
|---|---|
| Bandwidth Management | Restrict remote users to lower-bitrate versions |
| Tiered Access | Premium users get 4K, standard users get 1080p |
| Device Optimization | Mobile users automatically get mobile-optimized versions |
Add this repository to your Jellyfin instance for automatic updates:
- Go to Dashboard > Plugins > Repositories
- Click Add and enter:
- Name:
Quality Gate - URL:
https://geiserx.github.io/quality-gate/manifest.json
- Name:
- Go to Catalog and install Quality Gate
- Restart Jellyfin
Docker
VERSION="3.2.0.0"
curl -L -o QualityGate.zip \
"https://github.com/GeiserX/quality-gate/releases/download/v${VERSION}/quality-gate_${VERSION}.zip"
unzip QualityGate.zip -d /path/to/jellyfin/plugins/QualityGate/
docker restart jellyfinOr add to your docker-compose.yml:
volumes:
- ./plugins/QualityGate:/config/plugins/QualityGateLinux (Native)
VERSION="3.2.0.0"
curl -L -o QualityGate.zip \
"https://github.com/GeiserX/quality-gate/releases/download/v${VERSION}/quality-gate_${VERSION}.zip"
sudo unzip QualityGate.zip -d /var/lib/jellyfin/plugins/QualityGate/
sudo chown -R jellyfin:jellyfin /var/lib/jellyfin/plugins/QualityGate/
sudo systemctl restart jellyfinWindows
- Download the latest release
- Extract to
%LOCALAPPDATA%\jellyfin\plugins\QualityGate\ - Restart Jellyfin from Services or the tray icon
macOS
VERSION="3.2.0.0"
curl -L -o QualityGate.zip \
"https://github.com/GeiserX/quality-gate/releases/download/v${VERSION}/quality-gate_${VERSION}.zip"
unzip QualityGate.zip -d ~/.local/share/jellyfin/plugins/QualityGate/Navigate to Dashboard > Quality Gate to configure the plugin.
Policies define which filename patterns are allowed or blocked. Click "Add Policy" to create one.
| Field | Description |
|---|---|
| Policy Name | A descriptive name (e.g., "720p Only", "No 4K") |
| Allowed Filename Patterns | Regex patterns matched against the filename. Files must match at least one pattern. |
| Blocked Filename Patterns | Regex patterns matched against the filename. Matching files are always blocked. |
| Custom Intro Video | Optional intro video for users under this policy. Disable the built-in "Local Intros" plugin if you only want Quality Gate intros. |
| Enabled | Toggle policy on/off |
Choose a policy from the Default Policy dropdown. This applies to ALL users who don't have a specific override.
- Select (No default -- Full Access) to allow unrestricted access by default
- Select a policy to restrict all users by default
The User Access table shows all Jellyfin users and their current policy:
- Use Default -- inherits the default policy
- Full Access -- no restrictions
- Any named policy -- applies that policy's rules
If an override or the default policy points to a deleted or disabled policy, the dropdown shows DENIED until you choose a replacement (fail-closed). This applies to both per-user overrides and the default policy.
Evaluation order:
- Blocked Filename Patterns: If filename matches any blocked regex -- BLOCKED
- Allowed Filename Patterns: If defined and filename doesn't match any -- BLOCKED
- File existence: If the file doesn't exist on disk (dangling symlink) -- BLOCKED
- Otherwise -- ALLOWED
| Allowed Pattern | Blocked Pattern | Filename | Result |
|---|---|---|---|
- 720p |
-- | Movie (2021) - 720p.mkv |
Allowed |
- 720p |
-- | Movie (2021) - 2160p.mkv |
Blocked |
| (empty) | - 2160p|- 4K |
Movie (2021) - 1080p.mkv |
Allowed |
| (empty) | - 2160p|- 4K |
Movie (2021) - 2160p.mkv |
Blocked |
Tip: Patterns are case-insensitive regex with a 1-second timeout to prevent ReDoS. Jellyfin also supports bracketed labels (e.g.
Movie (2021) - [1080p].mkv), so use\[?1080p\]?to match both formats.
Policy Name: 720p Only
Allowed Filename Patterns:
- 720p
Only files with - 720p in the filename are visible.
Policy Name: No 4K
Blocked Filename Patterns:
- 2160p
- 4K
Everything is visible except 4K versions.
Policy Name: Standard
Blocked Filename Patterns:
- 2160p
- 4K
- Remux
- Create "Standard" policy (block 4K as above)
- Set Default Policy to "Standard"
- Add Full Access overrides for premium users
-
Result Filter: The plugin uses an ASP.NET Core
IAsyncResultFilterthat intercepts API responses before serialization, operating on C# objects directly. -
MediaSource Filtering: When Jellyfin returns media sources/versions to the client, the filter removes blocked versions so they don't appear in the UI.
-
Filename Matching: Each media version's filename is matched against your policy's regex patterns. For symlinked files, both the symlink filename and the resolved target filename are checked.
-
File Existence: Sources whose files don't exist on disk (e.g. dangling symlinks from in-progress transcodes) are automatically hidden, preventing playback errors.
All quality versions must be in the same Jellyfin library using Jellyfin's multi-version naming. Each version needs a - label suffix (space, hyphen, space, label):
movies/
Movie (2021)/
Movie (2021) - 2160p.mkv
Movie (2021) - 1080p.mkv
Movie (2021) - 720p.mkv
Jellyfin merges these into a single item with multiple MediaSources. The plugin then filters which sources each user can see.
- .NET 10.0 SDK
- Git
git clone https://github.com/GeiserX/quality-gate.git
cd quality-gate/Jellyfin.Plugin.QualityGate
dotnet build -c ReleaseThe compiled plugin will be in bin/Release/net10.0/.
- This plugin handles access control -- review your policies carefully
- Only administrators can configure policies
- See SECURITY.md for vulnerability reporting
Contributions are welcome! Please feel free to submit a Pull Request.
- Fork the repository
- Create your feature branch (
git checkout -b feature/AmazingFeature) - Commit your changes (
git commit -m 'Add some AmazingFeature') - Push to the branch (
git push origin feature/AmazingFeature) - Open a Pull Request
- smart-covers -- Cover extraction for books, audiobooks, comics, magazines, and music libraries with online fallback
- whisper-subs -- Automatically generates subtitles using local AI models powered by Whisper
- jellyfin-encoder -- Automatic 720p HEVC/AV1 transcoding service with optional symlink creation for Jellyfin multi-version support
- jellyfin-telegram-channel-sync -- Sync Jellyfin access with Telegram channel membership
This project is licensed under the GPL-3.0 License -- see the LICENSE file for details.
- Jellyfin -- The Free Software Media System
- The Jellyfin plugin development community