Intelligent media access control for Jellyfin
- Path-Based Policies -- Define granular access rules based on file path prefixes
- 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
- Detailed Logging -- Full audit trail of access decisions
This plugin is perfect for scenarios where you have:
| 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 |
| Storage Tiers | Keep originals on slow storage, transcodes on fast storage |
/media/Movies/ ← High-quality originals (4K/Remux)
/media-transcoded/Movies/ ← Transcoded versions (1080p/720p)
Create a "Standard Access" policy allowing only /media-transcoded/ and assign it to users who should be restricted.
Add this repository to your Jellyfin instance for automatic updates:
- Go to Dashboard → Plugins → Repositories
- Click Add and enter:
- Name:
Quality Gate - URL:
https://raw.githubusercontent.com/GeiserX/quality-gate/main/manifest.json
- Name:
- Go to Catalog and install Quality Gate
- Restart Jellyfin
Docker
# Download the latest release
curl -L -o QualityGate.zip \
https://github.com/GeiserX/quality-gate/releases/latest/download/quality-gate.zip
# Extract to your plugins volume
unzip QualityGate.zip -d /path/to/jellyfin/plugins/QualityGate/
# Restart your container
docker restart jellyfinOr add to your docker-compose.yml:
volumes:
- ./plugins/QualityGate:/config/plugins/QualityGateLinux (Native)
# Download the latest release
curl -L -o QualityGate.zip \
https://github.com/GeiserX/quality-gate/releases/latest/download/quality-gate.zip
# Extract to plugins directory
sudo unzip QualityGate.zip -d /var/lib/jellyfin/plugins/QualityGate/
# Set permissions
sudo chown -R jellyfin:jellyfin /var/lib/jellyfin/plugins/QualityGate/
# Restart Jellyfin
sudo systemctl restart jellyfinWindows
- Download the latest release
- Extract to
%LOCALAPPDATA%\jellyfin\plugins\QualityGate\ - Restart Jellyfin from Services or the tray icon
macOS
# Download the latest release
curl -L -o QualityGate.zip \
https://github.com/GeiserX/quality-gate/releases/latest/download/quality-gate.zip
# Extract to plugins directory
unzip QualityGate.zip -d ~/.local/share/jellyfin/plugins/QualityGate/
# Restart JellyfinNavigate to Dashboard → Quality Gate (or Dashboard → Plugins → Quality Gate) to configure the plugin.
Policies define which paths are allowed or blocked. Click "Add Policy" to create one.
| Field | Description |
|---|---|
| Policy Name | A descriptive name (e.g., "720p Only", "No 4K") |
| Allowed Path Prefixes | Paths users CAN access. Each prefix gets its own row; use Add Allowed Path for more. |
| Blocked Path Prefixes | Paths that will be blocked. Each prefix gets its own row; use Add Blocked Path for more. |
| Custom Intro Video | Optional intro video for users under this policy |
| 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. For each user, select a policy from the dropdown:
- Use Default — inherits the default policy above
- Full Access — no restrictions
- Any named policy — applies that policy's path rules
The Effective Access column shows what each user actually gets after considering overrides and defaults. Click Save to apply changes.
If an override points to a deleted or disabled policy, the dropdown stays on an explicit DENIED option until you choose a replacement. This preserves the server-side fail-closed behavior instead of silently widening access.
The plugin evaluates paths in this order:
- Blocked Paths: If file path starts with any blocked prefix -- BLOCKED
- Allowed Paths: If allowed paths are defined and file doesn't match any -- BLOCKED
- Otherwise -- ALLOWED
| Allowed Paths | Blocked Paths | File Path | Result |
|---|---|---|---|
/transcodes/ |
-- | /transcodes/Movie.mkv |
Allowed |
/transcodes/ |
-- | /originals/Movie.mkv |
Blocked |
| (empty) | /originals/4K/ |
/originals/1080p/Film.mkv |
Allowed |
| (empty) | /originals/4K/ |
/originals/4K/Film.mkv |
Blocked |
/media/ |
/media/4K/ |
/media/Movies/Film.mkv |
Allowed |
/media/ |
/media/4K/ |
/media/4K/Film.mkv |
Blocked |
Tip: If no Allowed Paths are set, all paths are allowed except those explicitly blocked.
Use case: You have originals in /mnt/originals/ and 720p transcodes in /mnt/transcodes/. Restrict some users to only see transcoded versions.
Policy Name: 720p Only
Allowed Path Prefixes:
/mnt/transcodes/
/mnt/remotes/transcodes/
Blocked Path Prefixes:
(leave empty)
Use case: Allow access to everything except 4K content stored in a specific folder.
Policy Name: No 4K
Allowed Path Prefixes:
(leave empty - allows all by default)
Blocked Path Prefixes:
/media/4K/
/media/UHD/
/mnt/storage/4K/
Use case: You have a Jellyfin library with multi-version support where originals and transcodes are in the same folder. Originals come from /mnt/originals/ and transcodes come from /mnt/transcodes/.
Policy Name: Standard Quality
Allowed Path Prefixes:
/mnt/transcodes/
Blocked Path Prefixes:
/mnt/originals/
Then set this as the Default Policy and add Full Access overrides for admin users.
Use case: Premium users get full access, standard users get 1080p max.
-
Create policy "Standard (1080p max)":
Blocked Path Prefixes: /media/4K/ /media/2160p/ -
Create policy "Premium (Full Access)" or use the built-in Full Access
-
Set Default Policy to "Standard (1080p max)"
-
Add User Overrides for premium users → "Full Access"
-
Result Filter: The plugin uses an ASP.NET Core
IAsyncResultFilterthat intercepts API responses before serialization. This operates on C# objects directly, avoiding the response compression issues that broke the previous middleware approach. -
MediaSource Filtering: When Jellyfin returns media sources/versions to the client, the filter removes blocked versions so they don't appear in the UI. This applies to both
PlaybackInfoand item detail responses. -
Path Matching: The plugin matches the full file path of each media version against your policy prefixes. Symlinks are resolved to check against the actual target path.
Important: Jellyfin resolves symlinks when indexing media. If your files are symlinks, the paths stored internally will be the resolved target paths, not the symlink paths. Your policies must use the resolved paths shown in Media Info -- not the mount point names you configured in your library.
To see what paths your files have:
- Go to a movie/show in Jellyfin
- Click the ⋮ menu → Media Info
- Look at the Path field for each version -- use these exact paths in your policies
Common path patterns:
- Docker:
/media/Movies/Title (2024)/Title.mkv - NFS mounts:
/mnt/nfs/media/Movies/... - SMB remotes:
/mnt/remotes/server/media/... - Transcodes on separate server:
/mnt/user/ShareMedia/Movies/...
Both high-quality originals and lower-quality transcodes must be in the same Jellyfin library (with multiple path entries). Do not create separate libraries per quality tier -- Jellyfin needs all versions as MediaSources on the same merged item for the plugin to filter them.
- .NET 9.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/net9.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
- 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