Smart episode management for Sonarr - Get episodes as you watch, clean up automatically when storage gets low.
REQUIRE_AUTH=true) or use VPN/reverse proxy with auth.
This project started as scratching my own itch - I wanted more granular series management and couldn't find exactly what I wanted. I'm not a programmer by trade, but I had a clear vision for the solution I needed. I used AI as a development tool to help implement my ideas faster, just like any other tool. The creativity, problem-solving, architecture decisions, and feature design are all mine - AI helped with code, syntax and implementation details. Although I run everything in my own production environment first, it is catered to my environment and is use at your own risk. All code is open source for anyone to review and audit. The tool has been useful for me, and I shared it in case others can benefit from it too - but I absolutely understand if some prefer to stick with established solutions.
- What It Does
- Quick Start
- Installation
- Plex Watchlist Sync
- Webhook Setup
- How to Use
- Features Explained
- Configuration Examples
- Troubleshooting
- Screenshots
- FAQ
- Support
Episeerr gives you four independent features for TV episode management:
| Feature | What It Does | Use Case |
|---|---|---|
| 🎯 Episode Selection | Choose specific episodes to download | Try pilots, skip seasons, selective downloads |
| ⚡ Viewing Automation | Next episode ready when you watch | Binge watching, always-ready episodes |
| 💾 Storage Management | Automatic cleanup based on time/viewing | Limited storage, inactive show cleanup |
| 🔄 Plex Watchlist Sync | Add to Plex watchlist, Episeerr handles the rest | Zero-effort adding, full selection control |
| 📌 Always Have | Baseline episodes always present and protected | Showcase libraries, permanent pilots, season placeholders |
Use one, some, or all - they work independently!
Get running in 5 minutes:
# 1. Create docker-compose.yml (minimal setup)
services:
episeerr:
image: vansmak/episeerr:latest
volumes:
- ./config:/app/config
- ./logs:/app/logs
- ./data:/app/data
ports:
- "5002:5002"
restart: unless-stopped
# 2. Start container
docker-compose up -d
# 3. Open http://your-server:5002/setup
# 4. Configure Sonarr, TMDB, and optional services
# 5. Create a rule, add a series, start watching!Restart container for changes to take effect
That's it! No .env file needed - configure everything via the GUI.
For automation: Set up webhooks ⬇️
- GUI Setup (Recommended) - Use
/setuppage, no.envfile needed - Environment Variables - Traditional
.envfile (still supported)
Create docker-compose.yml:
services:
episeerr:
image: vansmak/episeerr:latest
container_name: episeerr
environment:
# ============================================
# REQUIRED
# ============================================
- SONARR_URL=http://your-sonarr:8989
- SONARR_API_KEY=your_sonarr_api_key
- TMDB_API_KEY=your_tmdb_read_access_token
# ============================================
# OPTIONAL - For Viewing Automation
# ============================================
# Option 1: Tautulli (Plex)
- TAUTULLI_URL=http://your-tautulli:8181
- TAUTULLI_API_KEY=your_tautulli_key
# Option 2: Jellyfin (choose one mode below)
environment:
# --- Jellyfin: uncomment Option A OR Option B, not both ---
#
# Option A: Real-time (Jellyfin sends PlaybackProgress webhooks)
# Configure in Jellyfin: http://<episeerr>:5002/api/integration/jellyfin/webhook
# Notification type: PlaybackProgress
#
# - JELLYFIN_URL=http://your-jellyfin:8096
# - JELLYFIN_API_KEY=your_jellyfin_api_key
# - JELLYFIN_USER_ID=your_username
# - JELLYFIN_TRIGGER_MIN=50.0
# - JELLYFIN_TRIGGER_MAX=55.0
#
# Option B: Polling (Jellyfin sends PlaybackStart, Episeerr polls /Sessions)
# Configure in Jellyfin: http://<episeerr>:5002/api/integration/jellyfin/webhook
# Notification type: PlaybackStart
#
# - JELLYFIN_URL=http://your-jellyfin:8096
# - JELLYFIN_API_KEY=your_jellyfin_api_key
# - JELLYFIN_USER_ID=your_username
# - JELLYFIN_TRIGGER_PERCENTAGE=50.0
# - JELLYFIN_POLL_INTERVAL=900
# --- Emby: uncomment to enable ---
# Configure in Emby: User Prefs → Notifications → Webhooks
# URL: http://<episeerr>:5002/api/integration/emby/webhook
# Events: playback.start, playback.stop
#
# - EMBY_URL=http://your-emby:8096
# - EMBY_API_KEY=your_emby_api_key
# - EMBY_USER_ID=your_username
# - EMBY_TRIGGER_PERCENTAGE=50.0
# - EMBY_POLL_INTERVAL=900
# ============================================
# OPTIONAL - For Request Integration
# ============================================
- JELLYSEERR_URL=http://your-jellyseerr:5055
- JELLYSEERR_API_KEY=your_jellyseerr_key
# OR
- OVERSEERR_URL=http://your-overseerr:5055
- OVERSEERR_API_KEY=your_overseerr_key
# ============================================
# OPTIONAL - Authentication (Security)
# ============================================
# Uncomment to enable password protection:
# - REQUIRE_AUTH=true
# - AUTH_USERNAME=admin
# - AUTH_PASSWORD=your-password-here
# - SECRET_KEY=generate-random-key # Optional: auto-generated if not set
# ============================================
# OPTIONAL - Quick Links in Sidebar
# ============================================
- CUSTOMAPP_URL=http://192.168.1.100:8080
- CUSTOMAPP_NAME=My Custom App
- CUSTOMAPP_ICON=fas fa-cog
volumes:
- ./config:/app/config # Configuration files
- ./logs:/app/logs # Log files
- ./data:/app/data # Database and temp data
- ./temp:/app/temp # Temporary processing
ports:
- "5002:5002"
restart: unless-stoppedStart:
docker-compose up -dAccess:
http://your-server:5002
1. Add Custom Template
Create /boot/config/plugins/community.applications/private/episeerr/my-episeerr.xml:
<?xml version="1.0"?>
<Container version="2">
<Name>episeerr</Name>
<Repository>vansmak/episeerr:latest</Repository>
<Registry>https://hub.docker.com/r/vansmak/episeerr</Registry>
<Network>bridge</Network>
<Shell>sh</Shell>
<Privileged>false</Privileged>
<Support>https://github.com/Vansmak/episeerr/issues</Support>
<Project>https://github.com/Vansmak/episeerr</Project>
<Overview>Smart episode management for Sonarr</Overview>
<Category>MediaApp:Video</Category>
<WebUI>http://[IP]:[PORT:5002]</WebUI>
<Icon>https://raw.githubusercontent.com/Vansmak/episeerr/main/static/logo_icon.png</Icon>
<Config Name="WebUI Port" Target="5002" Default="5002" Mode="tcp" Description="Episeerr WebUI" Type="Port" Display="always" Required="true" Mask="false"/>
<Config Name="Config" Target="/app/config" Default="/mnt/user/appdata/episeerr/config" Mode="rw" Description="Configuration files" Type="Path" Display="always" Required="true" Mask="false"/>
<Config Name="Logs" Target="/app/logs" Default="/mnt/user/appdata/episeerr/logs" Mode="rw" Description="Log files" Type="Path" Display="always" Required="true" Mask="false"/>
<Config Name="Data" Target="/app/data" Default="/mnt/user/appdata/episeerr/data" Mode="rw" Description="Database files" Type="Path" Display="always" Required="true" Mask="false"/>
<Config Name="Temp" Target="/app/temp" Default="/mnt/user/appdata/episeerr/temp" Mode="rw" Description="Temporary files" Type="Path" Display="always" Required="false" Mask="false"/>
<Config Name="SONARR_URL" Target="SONARR_URL" Default="" Description="Sonarr base URL (e.g., http://sonarr:8989)" Type="Variable" Display="always" Required="true" Mask="false"/>
<Config Name="SONARR_API_KEY" Target="SONARR_API_KEY" Default="" Description="Sonarr API key" Type="Variable" Display="always" Required="true" Mask="true"/>
<Config Name="TMDB_API_KEY" Target="TMDB_API_KEY" Default="" Description="TMDB Read Access Token (not API key)" Type="Variable" Display="always" Required="true" Mask="true"/>
<Config Name="TAUTULLI_URL" Target="TAUTULLI_URL" Default="" Description="Tautulli URL (optional)" Type="Variable" Display="always" Required="false" Mask="false"/>
<Config Name="TAUTULLI_API_KEY" Target="TAUTULLI_API_KEY" Default="" Description="Tautulli API Key (optional)" Type="Variable" Display="always" Required="false" Mask="true"/>
<Config Name="JELLYFIN_URL" Target="JELLYFIN_URL" Default="" Description="Jellyfin URL (optional)" Type="Variable" Display="always" Required="false" Mask="false"/>
<Config Name="JELLYFIN_API_KEY" Target="JELLYFIN_API_KEY" Default="" Description="Jellyfin API Key (optional)" Type="Variable" Display="always" Required="false" Mask="true"/>
<Config Name="JELLYFIN_USER_ID" Target="JELLYFIN_USER_ID" Default="" Description="Jellyfin Username (required if using Jellyfin)" Type="Variable" Display="always" Required="false" Mask="false"/>
<Config Name="JELLYSEERR_URL" Target="JELLYSEERR_URL" Default="" Description="Jellyseerr URL (optional)" Type="Variable" Display="always" Required="false" Mask="false"/>
<Config Name="JELLYSEERR_API_KEY" Target="JELLYSEERR_API_KEY" Default="" Description="Jellyseerr API Key (optional)" Type="Variable" Display="always" Required="false" Mask="true"/>
</Container>2. Install from Apps
- Unraid → Apps
- Search "episeerr"
- Click Install
- Fill in required fields
The easiest way to configure Episeerr - no .env file needed!
Access: http://your-server:5002/setup
Configure:
- Sonarr - URL and API key (required) Initial setup Restart container for changes to take effect
- TMDB - API Read Access Token (required)
- Media Server - Choose Jellyfin, Emby, or Plex/Tautulli (optional)
- Overseerr/Jellyseerr - Request integration (optional)
Features:
- ✅ Test connections before saving
- ✅ Configuration stored in database
- ✅ Auto-populate Quick Links in sidebar
- ✅ No container restart needed
- ✅ Works alongside
.envfiles (database takes priority)
Migration from .env:
- Open
/setuppage - Your existing
.envvalues appear as defaults - Save to migrate to database
- Delete
.envfile when ready
Note: As of v3.2.0, environment variables are optional. You can configure everything via the /setup page GUI. Environment variables still work for backward compatibility and can be used alongside database configuration (database takes priority).
| Variable | Required | Description |
|---|---|---|
SONARR_URL |
❌ Optional* | Sonarr base URL (e.g., http://sonarr:8989) |
SONARR_API_KEY |
❌ Optional* | Sonarr API key (Settings → General) |
TMDB_API_KEY |
❌ Optional* | TMDB Read Access Token (Get one free) |
TAUTULLI_URL |
❌ Optional | For Plex viewing automation |
TAUTULLI_API_KEY |
❌ Optional | Tautulli API key |
JELLYFIN_URL |
❌ Optional | For Jellyfin viewing automation |
JELLYFIN_API_KEY |
❌ Optional | Jellyfin API key |
JELLYFIN_USER_ID |
Your Jellyfin username | |
JELLYSEERR_URL |
❌ Optional | For request integration |
JELLYSEERR_API_KEY |
❌ Optional | Jellyseerr API key |
| EMBY_USER_ID | EMBY_URL | ❌ Optional | For request integration |
| EMBY_API_KEY | ❌ Optional | EMBY API key |
Authentication (Optional):
| Variable | Default | Description |
|---|---|---|
REQUIRE_AUTH |
false |
Enable password authentication |
AUTH_USERNAME |
admin |
Login username |
AUTH_PASSWORD |
- | Login password (required if auth enabled) |
SECRET_KEY |
Auto-generated | Session encryption key (optional, auto-generated if not set) |
AUTH_BYPASS_LOCALHOST |
true |
Skip authentication for localhost access |
AUTH_SESSION_TIMEOUT |
86400 |
Session timeout in seconds (24 hours) |
- TMDB requires the Read Access Token, not the API key v3
- Jellyfin requires
JELLYFIN_USER_IDto be set to your username - All URLs should NOT have trailing slashes
- Security: If exposing Episeerr beyond your local network, enable
REQUIRE_AUTHor use a reverse proxy with authentication (Cloudflare Access, Authelia, etc.)
Dashboard Integrations Overview Episeerr's beta plugin system allows you to connect additional services that display statistics on your dashboard. Services are configured through the Setup page and automatically appear once configured. Available Integrations
Example, Radarr: Movie library management
Displays total movies and storage usage Shows monitored vs total counts
Setup Process
Navigate to Setup: Go to the Setup page (/setup) Find Integration: Scroll to "Dashboard Integrations" section Configure Service:
URL: Full service URL including http:// or https:// API Key: Found in service settings (usually under Settings > General)
Test Connection: Click "Test" button to verify Save: Click "Save" to store configuration Restart: Restart the Episeerr container Verify: Check Dashboard for new statistics pill Quick Link: Service link automatically appears in sidebar
Important Notes
Container restart required after initial configuration Configuration persists across restarts Services can be reconfigured at any time through Setup page Invalid configurations won't crash the dashboard - they simply won't display
Creating Custom Integrations Advanced users can create custom integrations for any service with an API:
Copy Template: Start with /integrations/_INTEGRATION_TEMPLATE.py Customize: Fill in service details, API calls, and widget configuration Save: Name file yourservice.py (no underscore prefix) Restart: Restart container to load new integration Configure: Service automatically appears in Setup page
The template includes extensive documentation and examples for:
Media library services (similar to Radarr) Download clients (qBittorrent, Transmission, etc.) Indexers and search services (Prowlarr, Jackett, etc.) Custom services with unique requirements
Add something to your Plex watchlist and Episeerr takes care of the rest.
- TV shows → get the
episeerr_selecttag in Sonarr → appear in Pending Requests → you pick a rule or specific episodes before anything downloads - Movies → go straight to Radarr (no selection step needed)
Optional: Auto-remove movies from Radarr after you've watched them, with a configurable grace period.
- Go to
http://your-server:5002/setup - Scroll to the Plex section under Dashboard Integrations
- Enter your Plex URL (e.g.,
http://plex:32400) and Plex Token - In the Watchlist Auto-Sync section below the connection fields:
- Enable automatic sync
- Set your sync interval (default: 2 hours)
- Optionally enable movie cleanup with a grace period
- Click Save
Prerequisites: The
episeerr_selectdelayed release profile in Sonarr must be set up or TV shows will start downloading immediately. See Episode Selection setup.
A helper script is included in the repo. It requires the requests library.
python get_plex_token.pyEnter your Plex username (not email) and password when prompted. The token printed works for both local server access and the Plex.tv watchlist API.
Manual method (no script):
- Sign in to plex.tv in a browser
- Open any media item
- Click the
···menu → Get Info - In the URL bar you'll find
X-Plex-Token=YOURTOKEN
| What You Do | What Episeerr Does |
|---|---|
| Add TV show to Plex watchlist | Creates a pending selection request, tags series in Sonarr with episeerr_select |
| Add movie to Plex watchlist | Sends directly to Radarr |
| Watch a movie (if cleanup enabled) | Schedules Radarr deletion after grace period |
Sync runs on your configured interval. Items already in Sonarr/Radarr are skipped. Items already in your pending requests are not duplicated.
When Delete movies after watched is enabled:
- Episeerr checks for watched movies in your Plex library
- Movies watched more than Grace Period days ago are removed from Radarr
- Only movies that were added via watchlist sync are eligible
Webhooks let Episeerr respond to events automatically. You only need the webhooks for features you want to use.
Enables: Tag processing, auto-assignment, series addition detection
Setup:
-
Sonarr → Settings → Connect → Add → Webhook
-
Configure:
- Name: Episeerr
- URL:
http://your-episeerr:5002/sonarr-webhook - Method: POST
- Triggers: Enable ONLY "On Series Add" and "on Grab"
-
Save
[Sonarr webhook configuration screen]
- Shows URL field
- Shows "On Series Add" checkbox
- Shows Save button
Test it:
# Add a series in Sonarr and check logs
docker logs episeerr | grep "Received Sonarr webhook"Enables: Next episode ready when you watch
Configuration:
Option 1: Setup Page (Recommended) - v3.2.0+
- Go to
http://your-episeerr:5002/setup - Scroll to Tautulli section
- Enter Tautulli URL and API Key
- Click Test Connection to verify
- Save
Option 2: Environment Variables
- TAUTULLI_URL=http://your-tautulli:8181
- TAUTULLI_API_KEY=your_tautulli_api_keyWebhook Setup:
-
Tautulli → Settings → Notification Agents → Add → Webhook
-
Configure Webhook:
- Webhook URL:
http://your-episeerr:5002/webhook - Webhook Method: POST
- Webhook URL:
-
Configure Triggers:
- Triggers: Enable ONLY "Watched"
- Conditions: (Leave default)
-
Configure Data:
Text:
{ "plex_title": "{show_name}", "plex_season_num": "{season_num}", "plex_ep_num": "{episode_num}", "thetvdb_id": "{thetvdb_id}", "themoviedb_id": "{themoviedb_id}" } -
Save
[Tautulli webhook configuration - Configuration tab]
[Tautulli webhook configuration - Triggers tab]
[Tautulli webhook configuration - Data tab with JSON]
Important Settings:
In Tautulli → Settings → General:
- TV Episode Watched Percent: Set between 50-95% (recommended: 80%)
Test it:
# Watch an episode to 80% and check logs
docker logs episeerr | grep "Received webhook"Enables: Next episode ready when you watch
Episeerr supports two modes for Jellyfin — pick one:
Configuration (for both modes):
Option 1: Setup Page (Recommended) - v3.2.0+
- Go to
http://your-episeerr:5002/setup - Scroll to Jellyfin section
- Choose your mode and enter settings accordingly
- Click Test Connection to verify
- Save
Option 2: Environment Variables - See each mode below for specific variables
Jellyfin sends a webhook on every progress update. Episeerr fires once when progress lands in the 50–55% window. No polling needed.
Webhook Setup:
- Jellyfin → Dashboard → Plugins → Webhooks → Add Generic Destination
- Configure:
- Webhook Name: Episeerr Episode Tracking
- Webhook URL:
http://your-episeerr:5002/api/integration/jellyfin/webhook - Notification Type: Select ONLY "Playback Progress"
- User Filter: Your username (recommended)
- Item Type: Episodes
- Send All Properties: ✅ Enabled
- Save
Environment Variables (if not using Setup Page):
- JELLYFIN_URL=http://your-jellyfin:8096
- JELLYFIN_API_KEY=your_api_key
- JELLYFIN_USER_ID=your_username # REQUIRED
- JELLYFIN_TRIGGER_MIN=50.0
- JELLYFIN_TRIGGER_MAX=55.0
[Jellyfin webhook plugin configuration]
[Shows Playback Progress selected]
[Shows User Filter field]
Jellyfin sends a webhook on session start. Episeerr then polls the Jellyfin /Sessions API every 15 minutes until the trigger percentage is hit. Useful if PlaybackProgress webhooks are unreliable on your setup.
Webhook Setup:
- Jellyfin → Dashboard → Plugins → Webhooks → Add Generic Destination
- Configure:
- Webhook URL:
http://your-episeerr:5002/api/integration/jellyfin/webhook - Notification Type: Select "Session Start"
- User Filter: Your username
- Item Type: Episodes
- Webhook URL:
Environment Variables (if not using Setup Page):
- JELLYFIN_URL=http://your-jellyfin:8096
- JELLYFIN_API_KEY=your_api_key
- JELLYFIN_USER_ID=your_username # REQUIRED
- JELLYFIN_TRIGGER_PERCENTAGE=50.0
- JELLYFIN_POLL_INTERVAL=900 # seconds (15 minutes)Which Jellyfin mode should you use?
| Option | Best For | Processing | Jellyfin Webhooks Needed |
|---|---|---|---|
| A: Real-Time | Most users | Immediate at 50–55% | PlaybackProgress (continuous) |
| B: Polling | Unreliable progress webhooks | Up to 15-min delay | Session Start (one-shot) |
Test it:
# Watch an episode past 50% and check logs
docker logs episeerr | grep "Processing Jellyfin"Enables: Next episode ready when you watch
Emby doesn't send continuous progress webhooks like Jellyfin's PlaybackProgress, so Episeerr uses polling only. On playback.start, Episeerr spawns a background thread that queries the Emby /Sessions API every 15 minutes until your watch progress hits the trigger threshold. This handles autoplay correctly — if E1 finishes and E2 auto-starts without a playback.stop firing for E1, the poll already caught E1 at 50% and triggered the next episode search.
Configuration:
Option 1: Setup Page (Recommended) - v3.2.0+
- Go to
http://your-episeerr:5002/setup - Scroll to Emby section
- Enter:
- Emby URL (e.g.,
http://emby:8096) - API Key (from Emby → Settings → Advanced → Security)
- Username (must match the user watching content)
- Trigger Percentage (default: 50.0%)
- Poll Interval (default: 900 seconds / 15 minutes)
- Emby URL (e.g.,
- Click Test Connection to verify
- Save
Option 2: Environment Variables
- EMBY_URL=http://your-emby:8096
- EMBY_API_KEY=your_emby_api_key
- EMBY_USER_ID=your_username # REQUIRED — must match the Emby user
- EMBY_TRIGGER_PERCENTAGE=50.0
- EMBY_POLL_INTERVAL=900 # seconds (15 minutes)Webhook Setup:
- Emby → User Preferences (top-right avatar) → Notifications → Webhooks → Add Webhook
- Configure:
- Webhook Name: Episeerr Episode Tracking
- Webhook URL:
http://your-episeerr:5002/api/integration/emby/webhook - Events: Enable "playback.start" and "playback.stop"
- (No item type filter in Emby — it sends all events; Episeerr filters to Episodes internally)
- Save
Note: The webhook is configured per-user in Emby, not server-wide like Jellyfin's plugin. Make sure you're configuring it for the user account that watches content.
Test it:
# Watch an episode past 50% and check logs
docker logs episeerr | grep "Processing Emby"How it works:
| Event | What Episeerr Does |
|---|---|
playback.start |
Starts polling /Sessions for this session every POLL_INTERVAL seconds |
Poll hits TRIGGER_PERCENTAGE |
Fires episode processing, marks session as handled |
playback.stop |
Stops the polling thread. If already processed by poll, skips. If not yet hit threshold, checks final position one last time. |
Test it:
# Watch an episode past 50% and check logs
docker logs episeerr | grep "Processing Emby"Enables: Season-specific automation with direct rule tags
What it does:
- Captures which season you requested
- Allows rules to start from that season (not Season 1)
Setup:
-
Jellyseerr/Overseerr → Settings → Notifications → Webhooks
-
Add Webhook:
- Webhook URL:
http://your-episeerr:5002/api/integration/seerr/webhook - Notification Types: Enable "Request Approved"
- Webhook URL:
-
Save
[Jellyseerr webhook configuration]
[Shows URL field and Request Approved checkbox]
Test it:
# Request a series in Jellyseerr and check logs
docker logs episeerr | grep "Stored.*request"Rules control what happens when you watch episodes.
-
Open Episeerr:
http://your-server:5002 -
Go to Rules → Create New Rule
-
Configure:
Setting What It Does Example Name Rule identifier "binge_watcher" GET Episodes to prepare "3 episodes" = next 3 ready KEEP Episodes to retain "1 episode" = keep only last watched Action Monitor or Search "Search" = actively download -
Optional Time-Based Cleanup:
Setting What It Does Example Grace Watched Delete old watched episodes after X days "7 days" Grace Unwatched Delete unwatched episodes after X days "14 days" Dormant Delete everything after X days inactive "30 days" -
Mark as Default Rule (if this is your main rule)
-
Save
[Rule creation form showing all fields]
[Example configuration for binge watcher]
Best for: Letting Sonarr/Jellyseerr control initial downloads
-
Enable in Episeerr:
- Settings → Global Settings
- Enable "Auto-assign new series to default rule"
-
Add series normally in Sonarr (no tags)
-
Series automatically joins default rule
-
Waits for first watch before processing
Use case: You request Season 3 from Jellyseerr → Let it download → Episeerr manages after first watch
Best for: Immediate processing with specific rules
-
In Sonarr, add series with tag
episeerr_[rule_name]- Example:
episeerr_binge_watcher - Example:
episeerr_one_at_a_time
- Example:
-
Episeerr processes immediately:
- Applies GET rule
- Monitors/searches episodes
- Removes tag
Use case: You want a specific rule applied right away
Best for: Existing series or manual control
-
Episeerr → Series Management
-
Select series → Choose rule → Assign
Use case: Adding existing series to Episeerr
Best for: Hands-off adding from Plex
- Enable Plex Watchlist Sync on the Setup page
- Add a show to your Plex watchlist
- On the next sync cycle, Episeerr creates a pending request for TV shows, or sends movies straight to Radarr
Use case: Browse Plex Discover, add to watchlist, Episeerr handles the rest
Choose specific episodes manually across seasons.
-
Sonarr → Settings → Profiles → Release Profiles → Add
-
Configure:
- Name: Episeerr Episode Selection Delay
- Delay:
10519200(20 years) - Tags:
episeerr_select
-
Save
[Sonarr release profile configuration]
[Shows delay field set to 10519200]
Method A: Sonarr tag (new series)
- Add series to Sonarr with
episeerr_selecttag - Episeerr → Pending Items → Select Seasons
- Choose specific episodes
- Submit → Only those episodes monitored
Method B: Series page icon (existing series)
- Episeerr → Series (grid or manage view)
- Click the list icon on any poster (grid) or in the Actions column (table)
- You're taken straight to the selection page for that show
Method C: Plex Watchlist Sync
- Add a TV show to your Plex watchlist
- On the next sync, a pending request is created automatically
- Go to Pending Items → Select Seasons and episodes
When a show enters the selection flow (from any method above), the season selection page shows a rule dropdown at the top.
Two options:
| Option | What It Does |
|---|---|
| Apply Rule | Assigns the rule for ongoing management — no immediate downloads; the rule governs future watch events |
| Select seasons/episodes below | Manually choose what to download; the selected rule is still assigned for ongoing management |
The rule dropdown pre-selects the show's current rule if it already has one — so re-routing a series to a different rule is just a one-click change.
Hands-off adding from your Plex watchlist.
Use cases:
- Browse Plex Discover and add without touching Sonarr
- Automatic movie requests to Radarr
- Clean up watched movies automatically
How it works:
- Add show/movie to Plex watchlist
- Episeerr polls on your configured interval
- TV → pending selection request +
episeerr_selecttag in Sonarr - Movie → sent directly to Radarr
- (Optional) Watched movies removed from Radarr after grace period
Manual episode picking across multiple seasons.
Use cases:
- Try pilots without downloading full seasons
- Skip filler episodes
- Download specific arcs
- Selective backlog management
- Re-route an existing series to a different rule
How it works:
- Series enters selection flow (tag, watchlist sync, or series page icon)
- Season selection page appears with a rule picker at the top
- Either apply a rule directly (no manual picking needed), or choose specific episodes below
- Only selected episodes download; the chosen rule handles ongoing management
Define a baseline of episodes that are always present and protected from cleanup.
This is about setting up the show, not ongoing watching. When a show enters a rule with Always Have, those episodes get downloaded immediately. Grace and Keep cleanup will never touch them — only Dormant (which is intentionally nuclear) overrides this.
Expression syntax:
| Expression | Result |
|---|---|
s1e1 |
Just the pilot |
s1 |
All of season 1 |
s1, s*e1 |
Season 1 + first ep of every other season |
s1-3 |
Seasons 1 through 3 |
s1e1-5 |
Season 1, episodes 1-5 |
all |
Everything |
Combine with commas. Leave blank to skip. Always Have, Get, Keep, Grace, and Dormant are all independent — use any combination.
Next episode ready when you watch.
Use cases:
- Binge watching (always 2-3 episodes ahead)
- Weekly shows (stay current)
- Automatic queue management
How it works:
- Watch S1E5
- Webhook fires to Episeerr
- Rule applied: GET next 2 episodes
- S1E6, S1E7 now monitored/searched
- KEEP rule: Delete S1E1-S1E4 (outside keep window)
Example flow:
Watch E5 → Get E6, E7 → Keep E5 → Delete E1-E4
Automatic cleanup based on time and viewing activity.
Use cases:
- Limited storage (seedboxes, budget servers)
- Inactive show cleanup
- Abandoned series removal
How it works:
| Cleanup Type | Trigger | What It Does |
|---|---|---|
| Grace Watched | X days inactive | Deletes old watched episodes, keeps last as bookmark |
| Grace Unwatched | X days inactive | Deletes unwatched episodes, keeps first as bookmark |
| Dormant | X days + low storage | Deletes EVERYTHING from abandoned shows |
Storage Gate:
- Set threshold: "Keep 20GB free"
- Cleanup only runs when below threshold
- Stops when back above threshold
Bookmarks:
- Grace cleanup ALWAYS keeps at least 1 episode
- You never lose your viewing position
Profile: Always 3 episodes ahead, aggressive cleanup
Rule Name: binge_watcher
GET: 3 episodes
KEEP: 1 episode
Action: Search
Grace Watched: 7 days
Grace Unwatched: 14 days
Dormant: 30 daysWhat happens:
- Watch E5 → E6, E7, E8 ready
- Keep E5, delete E1-E4
- After 7 days inactive → Delete E5 (keeps bookmark)
- After 30 days → Delete entire show
Profile: Stay current, keep buffer
Rule Name: weekly
GET: 1 episode
KEEP: 3 episodes
Action: Monitor
Grace Watched: 30 days
Grace Unwatched: null
Dormant: 90 daysWhat happens:
- Watch E5 → E6 monitored
- Keep E3, E4, E5
- After 30 days → Cleanup old episodes
- Unwatched episodes never auto-deleted
Profile: Never delete, keep everything
Rule Name: protected
GET: All
KEEP: All
Action: Search
Grace Watched: null
Grace Unwatched: null
Dormant: nullWhat happens:
- Watch E5 → All future episodes monitored
- Nothing ever deleted
- Perfect for rewatchable favorites
Profile: Watch whole seasons, rotate
Rule Name: season_binger
GET: 1 season
KEEP: 1 season
Action: Search
Grace Watched: 14 days
Grace Unwatched: null
Dormant: 90 daysWhat happens:
- Watch S2E1 → All of S3 monitored
- Keep all of S2, delete S1
- After 14 days → Cleanup S2
- Perfect for binging complete seasons
Profile: Plex shows all seasons exist without downloading everything
Rule Name: showcase
Always Have: s1, s*e1
GET: 1 episode
KEEP: 1 episode
Action: Search
Grace Watched: null
Grace Unwatched: null
Dormant: nullWhat happens:
- Show added → Season 1 downloads + first episode of every other season
- Plex displays all seasons so users see the full scope of the show
- When someone starts watching → Get 1 brings the next episode
- Always Have episodes never get deleted by Keep or Grace
- No cleanup configured — show persists as a library placeholder
Profile: Minimal footprint, always keep a starting point
Rule Name: one_at_a_time
Always Have: s1e1
GET: 1 episode
KEEP: 1 episode
Action: Search
Keep Pilot: true
Grace Watched: 14 days
Dormant: 60 daysWhat happens:
- Pilot is always protected (Always Have + Keep Pilot)
- Watch E5 → E6 ready, E4 deleted
- After 14 days inactive → Cleanup watched, pilot stays
- After 60 days dormant → Everything deleted including pilot
Check:
docker logs episeerrCommon issues:
- Missing required environment variables
- Invalid Sonarr URL format (remove trailing slash)
- Wrong TMDB key type (need Read Access Token, not API key)
Fix:
# Correct format:
- SONARR_URL=http://sonarr:8989 # No trailing slash
- TMDB_API_KEY=eyJhbG... # Read Access Token (long string)Test webhook reception:
# Watch logs live
docker logs -f episeerr | grep webhook
# Check recent webhook events
docker logs episeerr | grep "Received.*webhook" | tail -20Common issues:
| Problem | Check | Solution |
|---|---|---|
| No webhooks received | Network connectivity | Can webhook sender reach Episeerr? |
| Webhooks received but nothing happens | Series assignment | Is series in a rule? |
| Wrong episodes managed | Webhook data | Check logs for series name matching |
Verify webhook URLs:
- Sonarr:
http://episeerr:5002/sonarr-webhook - Tautulli:
http://episeerr:5002/webhook - Jellyfin:
http://episeerr:5002/api/integration/jellyfin/webhook - Emby:
http://episeerr:5002/api/integration/emby/webhook - Jellyseerr:
http://episeerr:5002/api/integration/seerr/webhook
Configuration: Use the
/setuppage to configure services with URLs and API keys (recommended), or use environment variables.
Check series assignment:
Episeerr → Series Management
Verify:
- Series is listed under a rule
- Rule has GET settings configured
- Watch an episode to trigger
Manual trigger:
# Watch an episode, then check logs
docker logs episeerr | grep "Monitored.*episodes"For direct rule tags (episeerr_binge_watcher):
-
Verify tag exists in Sonarr:
- Sonarr → Settings → Tags
- Tag must match rule name exactly
-
Check Sonarr webhook:
- Settings → Connect → Webhook
- URL:
http://episeerr:5002/sonarr-webhook - Trigger: "On Series Add" enabled
-
Check logs:
docker logs episeerr | grep "Processing.*with tag"
Most common issue: Missing JELLYFIN_USER_ID
# REQUIRED for Jellyfin
- JELLYFIN_USER_ID=your_username # This is your Jellyfin login nameCheck webhook plugin:
Jellyfin → Dashboard → Plugins → Webhooks
Verify:
- Webhook URL is correct
- Proper notification types selected
- User filter matches your username
Test:
# Watch episode past 50% and check logs
docker logs episeerr | grep "Jellyfin"
Q: Do I need Tautulli for Plex watchlist sync? A: No. Watchlist sync uses the Plex.tv API directly with your Plex token — Tautulli is only needed for viewing automation (next episode ready when you watch).
Q: Where do I get my Plex token?
A: Run python get_plex_token.py from the repo. Enter your Plex username (not email) and password. See Getting Your Plex Token for a manual method too.
Q: Why does my username not work in get_plex_token.py? A: Use your Plex username, not your email address. Check your username at plex.tv/account.
Q: TV shows from my watchlist aren't downloading automatically — is that right?
A: Yes, by design. TV shows get the episeerr_select tag and land in Pending Requests so you can choose a rule or pick specific episodes first. Movies go straight to Radarr with no selection step.
Q: Can I change the sync interval? A: Yes — Setup page → Plex section → Sync Interval. Options range from 30 minutes to 24 hours.
Q: Do I need all the webhooks?
A: No! Only set up webhooks for features you want:
- Episode Selection only: Sonarr webhook
- Viewing Automation: Sonarr + Tautulli/Jellyfin webhooks
- Full automation: All webhooks
Q: What does Always Have do?
A: It's an expression on a rule that defines episodes to always keep. When a show enters the rule, those episodes get downloaded immediately. Grace and Keep cleanup won't delete them. Only Dormant (which is intentionally nuclear) overrides it.
Q: Does Always Have apply when I move a show to a different rule?
A: Yes. Whether it's a new show or a reassignment, the Always Have expression runs and ensures those episodes are monitored.
Q: Will Always Have re-download episodes I deleted manually?
A: Not automatically. Always Have runs on rule assignment and protects during cleanup. It doesn't continuously scan for missing episodes.
Q: Can I use both Tautulli and Jellyfin?
A: No need - choose one based on your media server (Plex = Tautulli, Jellyfin = Jellyfin webhook)
Q: What's the difference between tags and auto-assign?
A:
- Tags (
episeerr_[rule_name]): Immediate processing with specific rules - Auto-assign: Passive assignment, waits for first watch
Q: Will this download my entire library?
A: No! Only series assigned to rules are managed. Use episode selection or auto-assign to control what gets managed.
Q: Why did my tag disappear?
A: Tags are temporary signals. After processing, the tag is removed. Check Series Management to verify assignment succeeded.
Q: How do I use tags for specific rules?
A: Tag format is episeerr_[rule_name]. If you have a rule named "binge_watcher", use tag episeerr_binge_watcher.
Q: Can I change which rule a series uses?
A: Yes! Either:
- Change tag in Sonarr (tag drift detection will update Episeerr)
- Manually reassign in Series Management
Q: Episodes aren't updating when I watch?
A: Check:
- Is viewing webhook configured? (Tautulli or Jellyfin)
- Is series assigned to a rule?
- Check logs:
docker logs episeerr | grep webhook
Q: How much do I need to watch for it to trigger?
A:
- Tautulli: Set in Tautulli settings (50-95%, recommended 80%)
- Jellyfin: 50% by default (configurable via
JELLYFIN_TRIGGER_PERCENTAGE)
Q: Can different people watch different seasons?
A: Yes! Enable "Grace Period Scope: Per Season" in rule settings for independent season tracking.
Q: Will I lose my place if episodes get deleted?
A: No! Grace cleanup always keeps:
- Grace Watched: Last watched episode (bookmark)
- Grace Unwatched: First unwatched episode (resume point)
Q: How do I test without deleting anything?
A: Enable "Global Dry Run Mode" in Settings. Review deletions in Pending Deletions before approving.
Q: Why are episodes being deleted immediately?
A: KEEP rule deletes in real-time when watching. This is by design. To prevent this:
- Increase KEEP count
- Or disable KEEP entirely (set to "All")
Q: What's the difference between Grace and Dormant?
A:
- Grace: Time-based cleanup of specific episode types (watched/unwatched)
- Dormant: Nuclear option - deletes EVERYTHING from completely abandoned shows
Q: Which Jellyfin mode should I use?
A: Real-time (Playback Progress) for most users. Use polling if you have webhook reliability issues.
Q: Do I need to disable any modes?
A: No! System auto-detects based on environment variables. Just set the vars for your chosen mode.
Q: JELLYFIN_USER_ID - What do I put here?
A: Your Jellyfin username (the name you use to log in). This is REQUIRED for Jellyfin integration.
Q: How do I set up storage cleanup?
A: Settings → Global Settings → Set "Storage Threshold" (e.g., 20GB). Cleanup only runs when below threshold.
Q: Will it delete shows I'm actively watching?
A: No! Grace periods reset when you watch episodes. Only inactive shows are cleaned up.
Q: Can I protect certain shows?
A: Yes! Create a rule with empty Grace and Dormant settings, assign those shows to it.
- 📖 In-App Documentation:
http://your-episeerr:5002/documentation - 🐛 Report Issues: GitHub Issues
- 💬 Discussions: GitHub Discussions
- ☕ Support Development: Buy Me A Coffee
# Docker
docker logs episeerr
# Logs directory
./logs/app.log
# Live monitoring
docker logs -f episeerr# Check webhook reception
docker logs episeerr | grep "Received.*webhook"
# Check rule processing
docker logs episeerr | grep "Monitored.*episodes"
# Check errors
docker logs episeerr | grep "Error\|Failed"
# Check specific series
docker logs episeerr | grep "Breaking Bad"Contributions welcome! Please open an issue or pull request on GitHub.
Built with AI assistance as a development tool. All architecture, design decisions, and problem-solving are human-driven. Code is open source for transparency and community review.
Ready to get started? Jump to Quick Start ⬆️




