PrunArr
Automatically clean up your Radarr and Sonarr libraries based on what you've actually watched in Plex/Jellyfin (via Tautulli).
Stop manually managing your media library. PrunArr removes watched content after a configurable period, checks streaming availability, and gives you complete control over what stays and what goes.
Quick Start
# 1. Install
pip install prunarr
# 2. Configure (create config.yaml with your API keys)
curl -O https://raw.githubusercontent.com/haijeploeg/prunarr/main/config.example.yaml
mv config.example.yaml config.yaml
# Edit config.yaml with your API keys
# 3. Preview what would be removed
prunarr --config config.yaml movies remove --dry-run
# 4. Remove watched content (60+ days old by default)
prunarr --config config.yaml movies remove
prunarr --config config.yaml series remove
Why PrunArr?
The Problem:
- Your media library keeps growing
- You're running out of storage space
- Manually tracking what's been watched is tedious
- You don't know what's safe to remove
- There are Movies and Shows in your library that are also availble on streaming providers
The Solution: PrunArr automates media cleanup by:
- ✅ Checking Tautulli to see what's been watched
- ✅ Removing content after your specified retention period
- ✅ Checking if content is available on streaming services
- ✅ Supporting user-based tracking for multi-user setups
- ✅ Providing safety features (dry-run, confirmations, previews)
Perfect for:
- People with limited storage space
- Multi-user Plex/Jellyfin servers
- Users of Overseerr request management
- Anyone tired of manual library cleanup
- Users who want to prioritize unique content over streamable content
Key Features
🎯 User-Based Tracking
Integrates with Overseerr to automatically track who requested what. Content is only removed when watched by the original requester.
prunarr movies remove --username "alice" --days-watched 30
⏰ Flexible Retention Periods
Control exactly how long to keep watched content:
prunarr movies remove --days-watched 60 # Remove after 60 days
prunarr series remove --days-watched 90 # Keep series longer
📦 Size-Based Filtering
Target large files to free up space quickly:
prunarr movies list --min-filesize "5GB" --sort-by filesize --desc
prunarr movies remove --min-filesize "5GB" --days-watched 60
🏷️ Tag-Based Organization
Filter content by quality, genre, or any custom tags:
prunarr movies list --tag "4K" --tag "HDR"
prunarr movies remove --tag "Kids" --days-watched 14
prunarr movies remove --exclude-tag "Favorites"
🎬 Streaming Provider Integration
Check if content is available on your streaming services via JustWatch:
# Remove watched movies available on streaming
prunarr movies remove --on-streaming --days-watched 30
# Keep unique content longer (not on streaming)
prunarr movies remove --not-on-streaming --days-watched 180
📖 Streaming Integration Guide →
🛡️ Safety-First Design
Multiple layers of protection:
- Dry-run mode - Preview changes before committing
- Confirmation prompts - Review what will be removed
- User verification - Only remove content watched by the requester
- Detailed logging - Track all operations with
--debug
📊 Rich Console Output
Beautiful, informative tables with:
- 🟢 Color-coded status (Watched, Partial, Unwatched)
- 📏 Human-readable file sizes (MB, GB, TB)
- 📅 Last watched dates and days ago
- 🔄 JSON output option for automation
⚡ Performance & Automation
- Intelligent caching - Minimize API calls
- JSON output - Machine-readable for scripts
- Cron-ready - Perfect for scheduled automation
- Exit codes - Proper status codes for monitoring
Documentation
Getting Started
- Installation Guide - Install PrunArr via pip or from source
- Configuration Guide - Set up API keys and options
- Quick Start Guide - Get productive in minutes
- Command Reference - Complete command documentation
Core Concepts
- Tag System - User tracking and content organization
- Streaming Integration - JustWatch provider integration
- Advanced Features - Automation, scripting, and optimization
- Troubleshooting - Common issues and solutions
Common Use Cases
Weekly Cleanup Routine
# Preview and remove watched content
prunarr movies remove --days-watched 60 --dry-run
prunarr movies remove --days-watched 60
prunarr series remove --days-watched 90
Free Up Space Quickly
# Target large files first
prunarr movies list --min-filesize "10GB" --sort-by filesize --desc
prunarr movies remove --min-filesize "5GB" --days-watched 30
Smart Streaming-Based Cleanup
# Remove watched movies you can re-stream
prunarr movies remove --on-streaming --days-watched 30
# Keep unique content longer
prunarr movies remove --not-on-streaming --days-watched 180
Multi-User Management
# List content by user
prunarr movies list --username "alice"
# User-specific cleanup
prunarr movies remove --username "bob" --days-watched 45
Kids Content Fast Rotation
# Quick cleanup of kids content
prunarr movies remove --tag "Kids" --days-watched 14
prunarr series remove --tag "Kids" --days-watched 14
Requirements
- Python 3.9 or higher
- Radarr (for movies) and/or Sonarr (for TV shows)
- Tautulli (for watch history tracking)
Deployment Options
📦 PyPI Installation (Recommended)
pip install prunarr
🐳 Docker
Run PrunArr in a container for isolated, portable deployments:
# Pull from GitHub Container Registry
docker pull ghcr.io/hploeg/prunarr:latest
# Run with Docker
docker run --rm \
-e RADARR_API_KEY="your-api-key" \
-e RADARR_URL="https://radarr.example.com" \
-e SONARR_API_KEY="your-api-key" \
-e SONARR_URL="https://sonarr.example.com" \
-e TAUTULLI_API_KEY="your-api-key" \
-e TAUTULLI_URL="https://tautulli.example.com" \
ghcr.io/hploeg/prunarr:latest movies list --limit 10
# Or use Docker Compose
docker-compose run --rm prunarr movies remove --dry-run
☸️ Kubernetes with Helm
Deploy to Kubernetes for automated, scheduled cleanups:
# Install from OCI registry
helm install prunarr oci://ghcr.io/hploeg/charts/prunarr \
--version 1.0.0 \
--set config.radarr.apiKey="your-api-key" \
--set config.radarr.url="https://radarr.example.com" \
--set config.sonarr.apiKey="your-api-key" \
--set config.sonarr.url="https://sonarr.example.com" \
--set config.tautulli.apiKey="your-api-key" \
--set config.tautulli.url="https://tautulli.example.com"
# Default: CronJob mode with daily cleanup at 2 AM (movies) and 3 AM (series)
Features:
- 🕐 Automated scheduling with Kubernetes CronJobs
- 💾 Persistent cache with PVC
- 🔒 Secret management for API keys
- 📊 Resource limits and health checks
- 🔄 Easy rollbacks and updates
📖 Kubernetes Deployment Guide →
Installation
From PyPI (Recommended)
pip install prunarr
From Source
git clone https://github.com/haijeploeg/prunarr
cd prunarr
pip install -e .
Configuration
-
Create config file:
curl -O https://raw.githubusercontent.com/haijeploeg/prunarr/main/config.example.yaml mv config.example.yaml config.yaml
-
Add your API keys:
radarr_api_key: "your-radarr-api-key" radarr_url: "https://radarr.yourdomain.com" sonarr_api_key: "your-sonarr-api-key" sonarr_url: "https://sonarr.yourdomain.com" tautulli_api_key: "your-tautulli-api-key" tautulli_url: "https://tautulli.yourdomain.com"
-
Test your config:
prunarr --config config.yaml movies list --limit 5
Overseerr Integration
PrunArr works seamlessly with Overseerr's "Tag Requests" feature:
- In Overseerr, go to Settings → Radarr/Sonarr
- Enable "Tag Requests"
- That's it! PrunArr will automatically track who requested what
When users request content through Overseerr:
- Tags are automatically created (e.g.,
"123 - john_doe") - PrunArr matches usernames with Tautulli
- Content is only removed when watched by the original requester
Command Overview
Movies:
prunarr movies list # List all movies
prunarr movies remove --dry-run # Preview removal
prunarr movies remove --days-watched 60 # Remove watched movies
Series:
prunarr series list # List all series
prunarr series get "Breaking Bad" # Get detailed info
prunarr series remove --days-watched 90 # Remove watched series
History:
prunarr history list --limit 20 # View watch history
Streaming:
prunarr providers list # List streaming providers
prunarr providers check "The Matrix" # Check availability
Cache:
prunarr cache init # Initialize cache
prunarr cache status # View cache stats
📖 Complete Command Reference →
Contributing
Contributions are welcome! Please:
- Fork the repository
- Create a feature branch (
git checkout -b feature/amazing-feature) - Make your changes
- Run tests (
make test) - Format code (
make format) - Commit (
git commit -m 'feat: add amazing feature') - Push (
git push origin feature/amazing-feature) - Open a Pull Request
Development Setup
git clone https://github.com/haijeploeg/prunarr
cd prunarr
python -m venv env
source env/bin/activate
pip install -e ".[dev]"
# Run tests
make test
# Format code
make format
# Run linting
make lint
Support
- Documentation: docs/
- Issues: GitHub Issues
- Discussions: GitHub Discussions
License
Apache-2.0 License - See LICENSE file for details.
Links
- GitHub: https://github.com/haijeploeg/prunarr
- PyPI: https://pypi.org/project/prunarr/
- Issues: https://github.com/haijeploeg/prunarr/issues
Made with ❤️ for the Plex/Jellyfin community
PrunArr is not affiliated with Radarr, Sonarr, Tautulli, Overseerr, Plex, or Jellyfin.
Metadata
Release files for prunarr 1.1.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | Provenance | Actions |
|---|---|---|---|---|
| prunarr-1.1.0.tar.gz | 102.7 kB | With provenance | Details for prunarr-1.1.0.tar.gz |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Actions Reset |
|---|---|---|---|---|
| prunarr-1.1.0-py3-none-any.whl | Python 3 | none | any | Details for prunarr-1.1.0-py3-none-any.whl |
Total release size: 223.5 kB
Release files / prunarr-1.1.0.tar.gz
| Download URL | prunarr-1.1.0.tar.gz |
|---|---|
| Size | 102.7 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
d26b24bb381776987e78ee61e084ab07e4b4731ee0f99e8edc19b24549bd947d
|
|
BLAKE2b-256 checksum How to use checksums |
1104130d34c336b925b6abcb16f8e29fe0285025b9d75705e96cc877c4c4a7cf
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/6.1.0 CPython/3.13.7
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 17, 2025.
Transparency logRelease files / prunarr-1.1.0-py3-none-any.whl
| Download URL | prunarr-1.1.0-py3-none-any.whl |
|---|---|
| Size | 120.8 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
93dd2a41fb744df00bd35b9a1cb74f0eac5161671799d1dae87e597f0c438d89
|
|
BLAKE2b-256 checksum How to use checksums |
c6ff6a7938858cc1db90a139ab1c83de64e27b7a0b6db3d9262d384ee91d7d76
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/6.1.0 CPython/3.13.7
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 17, 2025.
Transparency logSecurity for prunarr 1.1.0
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
Full provenance
Both files in this release have been uploaded with provenance metadata.
Attestation for prunarr-1.1.0.tar.gz
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 17, 2025.
Transparency logA note on package security
PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.
To learn more about protecting yourself from malicious actors, visit our package security documentation.