Episeerr

August 13, 2026 ยท View on GitHub

Smart episode management for Sonarr - Get episodes as you watch, clean up automatically when storage gets low. 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.

A note on support: the fast bug-fix turnaround this project has had depends on a paid AI subscription I use for development, which in turn depends on donations. See Support for details โ€” short version: if donations don't cover it, support slows down a lot.

Buy Me A Coffee


Docker Pulls GitHub Issues License: AGPL v3 Buy Me A Coffee


๐Ÿ“‹ Table of Contents


What It Does

Episeerr gives you four independent features for TV episode management:

FeatureWhat It DoesUse Case
๐ŸŽฏ Episode SelectionChoose specific episodes to downloadTry pilots, skip seasons, selective downloads
โšก Viewing AutomationNext episode ready when you watchBinge watching, always-ready episodes
๐Ÿ’พ Storage ManagementAutomatic cleanup based on time/viewingLimited storage, inactive show cleanup
๐Ÿ”„ Plex Watchlist SyncAdd to Plex watchlist, Episeerr handles the restZero-effort adding, full selection control
๐Ÿ“Œ Always HaveBaseline episodes always present and protectedShowcase libraries, permanent pilots, season placeholders

Use one, some, or all - they work independently!


Quick Start

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 โฌ‡๏ธ


Installation

Two Ways to Configure:

  1. GUI Setup (Recommended) - Use /setup page, no .env file needed
  2. Environment Variables - Traditional .env file (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 (pick ONE media server)
      # ============================================

      # Option A: Plex (recommended โ€” native webhooks, no Tautulli needed)
      #   Requires Plex Pass for webhook support.
      #   Configure webhook in Plex: Settings โ†’ Webhooks
      #   URL: http://<episeerr>:5002/api/integration/plex/webhook
      #   Detection mode + token configured via /setup page (GUI recommended)
      #
      # - PLEX_URL=http://your-plex:32400
      # - PLEX_TOKEN=your_plex_token

      # Option A2: Tautulli (Plex without Plex Pass, or for richer watch history)
      #   โš ๏ธ  Do NOT use alongside Plex native webhooks โ€” configure one or the other.
      #   Webhook URL: http://<episeerr>:5002/api/integration/tautulli/webhook
      #   Legacy URL:  http://<episeerr>:5002/webhook  (still supported)
      #
      # - TAUTULLI_URL=http://your-tautulli:8181
      # - TAUTULLI_API_KEY=your_tautulli_key

      # Option B: Jellyfin (choose mode A or B in /setup, not via env vars)
      #   Webhook URL: http://<episeerr>:5002/api/integration/jellyfin/webhook
      #   Notification type: PlaybackProgress (real-time) or Session Start (polling)
      #
      # - JELLYFIN_URL=http://your-jellyfin:8096
      # - JELLYFIN_API_KEY=your_jellyfin_api_key
      # - JELLYFIN_USER_ID=your_username
      # - JELLYFIN_TRIGGER_PERCENTAGE=50.0   # polling mode
      # - JELLYFIN_POLL_INTERVAL=900          # polling mode
      # - JELLYFIN_TRIGGER_MIN=50.0           # real-time mode
      # - JELLYFIN_TRIGGER_MAX=55.0           # real-time mode

      # Option C: Emby
      #   Webhook 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-stopped

Start:

docker-compose up -d

Access:

http://your-server:5002

GUI Setup Page (v3.2.0+)

The easiest way to configure Episeerr - no .env file needed!

Access: http://your-server:5002/setup

Configure:

  1. Sonarr - URL and API key (required) Initial setup Restart container for changes to take effect
  2. TMDB - API Read Access Token (required)
  3. Media Server - Choose Plex, Jellyfin, or Emby (optional โ€” needed for viewing automation)
  4. 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 .env files (database takes priority)

Migration from .env:

  1. Open /setup page
  2. Your existing .env values appear as defaults
  3. Save to migrate to database
  4. Delete .env file when ready

Environment Variables

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).

VariableRequiredDescription
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)
PLEX_URLโŒ OptionalPlex server URL (e.g. http://plex:32400)
PLEX_TOKENโŒ OptionalPlex authentication token
TAUTULLI_URLโŒ OptionalTautulli โ€” only needed for watch-history override or if you don't have Plex Pass
TAUTULLI_API_KEYโŒ OptionalTautulli API key
JELLYFIN_URLโŒ OptionalFor Jellyfin viewing automation
JELLYFIN_API_KEYโŒ OptionalJellyfin API key
JELLYFIN_USER_IDโš ๏ธ Required if using JellyfinYour Jellyfin username
EMBY_URLโŒ OptionalFor Emby viewing automation
EMBY_API_KEYโŒ OptionalEmby API key
EMBY_USER_IDโš ๏ธ Required if using EmbyYour Emby username
JELLYSEERR_URLโŒ OptionalFor request integration
JELLYSEERR_API_KEYโŒ OptionalJellyseerr API key

Authentication (Optional):

VariableDefaultDescription
REQUIRE_AUTHfalseEnable password authentication
AUTH_USERNAMEadminLogin username
AUTH_PASSWORD-Login password (required if auth enabled)
SECRET_KEYAuto-generatedSession encryption key (optional, auto-generated if not set)
AUTH_BYPASS_LOCALHOSTtrueSkip authentication for localhost access
AUTH_SESSION_TIMEOUT86400Session timeout in seconds (24 hours)

โš ๏ธ Important Notes:

  • TMDB requires the Read Access Token, not the API key v3
  • Jellyfin requires JELLYFIN_USER_ID to be set to your username
  • All URLs should NOT have trailing slashes
  • Security: If exposing Episeerr beyond your local network, enable REQUIRE_AUTH or 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

Plex Watchlist Sync

Add something to your Plex watchlist and Episeerr takes care of the rest.

  • TV shows โ†’ get the episeerr_select tag 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.


Setup

  1. Go to http://your-server:5002/setup
  2. Scroll to the Plex section under Dashboard Integrations
  3. Enter your Plex URL (e.g., http://plex:32400) and Plex Token
  4. 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
  5. Click Save

Prerequisites: The episeerr_select delayed release profile in Sonarr must be set up or TV shows will start downloading immediately. See Episode Selection setup.


Getting Your Plex Token

A helper script is included in the repo. It requires the requests library.

python get_plex_token.py

Enter 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):

  1. Sign in to plex.tv in a browser
  2. Open any media item
  3. Click the ยทยทยท menu โ†’ Get Info
  4. In the URL bar you'll find X-Plex-Token=YOURTOKEN

How It Works

What You DoWhat Episeerr Does
Add TV show to Plex watchlistCreates a pending selection request, tags series in Sonarr with episeerr_select
Add movie to Plex watchlistSends 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.


Movie Cleanup

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

Webhook Setup

Webhooks let Episeerr respond to events automatically. You only need the webhooks for features you want to use.

1. Sonarr Webhook (Required)

Enables: Tag processing, auto-assignment, series addition detection

Setup:

  1. Sonarr โ†’ Settings โ†’ Connect โ†’ Add โ†’ Webhook

  2. Configure:

    • Name: Episeerr
    • URL: http://your-episeerr:5002/sonarr-webhook
    • Method: POST
    • Triggers: Enable ONLY "On Series Add" and "on Grab"
  3. Save

Test it:

docker logs episeerr | grep "Received Sonarr webhook"

2. Plex Webhook (For Viewing Automation โ€” Plex Pass required)

Enables: Next episode ready when you watch, dashboard "Now Playing" widget, real-time movie watch detection

Episeerr now integrates with Plex directly โ€” no Tautulli required for episode detection. The media.scrobble and media.stop events cover both TV episodes and movies โ€” no additional Plex configuration needed for movie support.

โš ๏ธ Plex OR Tautulli for episode detection โ€” not both. Configure one webhook source per setup. Using both can cause double-processing.

Setup:

  1. Go to http://your-episeerr:5002/setup โ†’ Integrations โ†’ Plex
  2. Enter your Plex Server URL and Plex Token (how to get token)
  3. Choose your Detection Method (see table below)
  4. Click Test then Save

In Plex:

  • Plex Web / Desktop โ†’ Settings โ†’ Webhooks โ†’ Add Webhook
  • URL: http://your-episeerr:5002/api/integration/plex/webhook

Detection Modes:

ModeHow It WorksBest For
Scrobble (90%)Plex fires its native "watched" event. Episeerr processes immediately.Simple setup, reliable
Stop + ThresholdProcess when you stop at โ‰ฅ your % (e.g. 50%). Scrobble fires at 90% as a safety net if the stop event is missed. Episodes are never double-processed.Earlier triggering, no polling
PollingBackground thread polls /status/sessions every N minutes.Unreliable webhook environments

Allowed Users (optional): Enter comma-separated Plex usernames in setup to restrict processing to specific accounts. Leave blank for all users.

Test it:

docker logs episeerr | grep "\[Plex webhook\]"

2b. Tautulli Webhook (Optional โ€” Plex users who prefer Tautulli history)

Tautulli is now optional. Use it only if you want richer watch history from Tautulli instead of Plex's native API (e.g. for grace period and dormant detection accuracy).

โš ๏ธ If you use Plex native webhooks (above), do NOT also set up a Tautulli "Watched" webhook. Tautulli's "Watched" webhook and Plex's scrobble/stop webhooks both trigger episode processing โ€” running both will cause double downloads.

When to use Tautulli:

  • You want Tautulli's richer per-user watch history for grace period / dormant decisions
  • You don't have Plex Pass (no native webhooks available)

Setup:

  1. Go to http://your-episeerr:5002/setup โ†’ Integrations โ†’ Tautulli
  2. Enter Tautulli URL and API Key
  3. Optionally enable "Use Tautulli for watch history" โ€” routes all watch-date lookups (grace, dormant) through Tautulli
  4. Click Test then Save

Tautulli Webhook (only if using Tautulli for episode detection):

  1. Tautulli โ†’ Settings โ†’ Notification Agents โ†’ Add โ†’ Webhook
  2. Configure:
    • Webhook URL: http://your-episeerr:5002/api/integration/tautulli/webhook
    • Method: POST
    • Trigger: "Watched"
  3. Data โ†’ Text:
    {
      "plex_title": "{show_name}",
      "plex_movie_title": "{title}",
      "plex_season_num": "{season_num}",
      "plex_ep_num": "{episode_num}",
      "thetvdb_id": "{thetvdb_id}",
      "themoviedb_id": "{themoviedb_id}",
      "media_type": "{media_type}"
    }
    
  4. Save

media_type is required for movie support. Without it, Episeerr falls back to detecting movies by the absence of season/episode numbers, which works in most cases but media_type is the explicit, reliable signal.

Legacy URL: /webhook still works if you have an existing Tautulli setup โ€” just add "media_type": "{media_type}" to your existing template.

Optional: Playback Start activation (for + modifier rules)

If you use the + activation modifier (s*e1+, e1+, etc.) and want the hold released the moment you press play rather than after the watch threshold, add a second notification agent:

  1. Tautulli โ†’ Settings โ†’ Notification Agents โ†’ Add โ†’ Webhook
  2. Configure:
    • Webhook URL: http://your-episeerr:5002/api/integration/tautulli/webhook
    • Method: POST
    • Trigger: "Playback Start"
  3. Data โ†’ Text: (same template as above, plus a hardcoded notification_type โ€” this is what tells Episeerr it's a play-start event. Type it exactly as shown below, with no curly braces: Tautulli has no {notification_type} placeholder, so a template using braces here sends the literal, unsubstituted text {notification_type} and Episeerr will never recognize it as a play-start event.)
    {
      "plex_title": "{show_name}",
      "plex_movie_title": "{title}",
      "plex_season_num": "{season_num}",
      "plex_ep_num": "{episode_num}",
      "thetvdb_id": "{thetvdb_id}",
      "themoviedb_id": "{themoviedb_id}",
      "media_type": "{media_type}",
      "notification_type": "playback start"
    }
    
  4. Save

The "Watched" agent alone is enough โ€” Playback Start is purely optional. Unlike Jellyfin/Emby's polling mode, Tautulli's "Watched" trigger already fires on its own once your TV Episode Watched Percent setting (below) is crossed โ€” Episeerr never needs to poll anything for Tautulli. Adding "Playback Start" doesn't change whether or when watch detection happens; it only adds an earlier, optional prefetch. If you do configure it, Playback Start runs in prefetch-only mode: your get-count is applied, so the next episode is staged as soon as you start watching. Everything triggered by finishing an episode โ€” keep-window deletion, finale keep-release, sequential season advance, series-ended unmonitor โ€” is deferred to the "Watched" event, so nothing is ever deleted while you're mid-episode. Held series (+ modifier) are the exception: their activation episode releases the hold and processes fully on play start, as before. Since both agents fire for the same episode ~however long you take to finish it, the next episode may get searched for twice (once at start, once at watched) โ€” Sonarr no-ops the repeat, so this is harmless, just slightly redundant. Do not add "notification_type" to the "Watched" agent's template โ€” leave it out there, since a hardcoded "playback start" on the Watched agent would make watched events prefetch-only and never clean up.

In Tautulli โ†’ Settings โ†’ General, set TV Episode Watched Percent between 50โ€“95% (recommended: 80%).

webhookwebhook2 webhook3webhook4


3. Jellyfin Webhook (For Viewing Automation)

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+

  1. Go to http://your-episeerr:5002/setup
  2. Scroll to Jellyfin section
  3. Choose your mode and enter settings accordingly
  4. Click Test Connection to verify
  5. Save

Option 2: Environment Variables - See each mode below for specific variables


Mode A: PlaybackProgress (Advanced)

Jellyfin sends a webhook on every progress update. Episeerr fires once when progress lands in the 50โ€“55% window. No polling needed, but Jellyfin fires this webhook continuously during playback (webhook spam) โ€” Mode B is recommended for most users.

Webhook Setup:

  1. Jellyfin โ†’ Dashboard โ†’ Plugins โ†’ Webhooks โ†’ Add Generic Destination
  2. 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 AND โœ… Movies โ€” without Movies checked, movie watch dates are not recorded in real-time
    • Send All Properties: โœ… Enabled
    • Add Request Header: Key: Content-Type (hyphen, not underscore), Value: application/json โ€” required or Jellyfin's webhook will not be accepted
  3. 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
image image
[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. The webhook fires once instead of continuously, so this is the recommended default.

Webhook Setup:

  1. Jellyfin โ†’ Dashboard โ†’ Plugins โ†’ Webhooks โ†’ Add Generic Destination
  2. Configure:
    • Webhook URL: http://your-episeerr:5002/api/integration/jellyfin/webhook
    • Notification Type: Select "Session Start" and "Playback Stop"
    • User Filter: Your username
    • Item Type: โœ… Episodes AND โœ… Movies โ€” without Movies checked, movie watch dates are not recorded in real-time
    • Add Request Header: Key: Content-Type (hyphen, not underscore), Value: application/json โ€” required or Jellyfin's webhook will not be accepted

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?

OptionBest ForProcessingJellyfin Webhooks Needed
A: PlaybackProgress (Advanced)Immediate triggering, tolerant of webhook spamImmediate at 50โ€“55%PlaybackProgress (continuous)
B: Webhook-Triggered Polling (Recommended)Most usersUp to 15-min delaySession Start (one-shot)

Test it:

# Watch an episode past 50% and check logs
docker logs episeerr | grep "Processing Jellyfin"

4. Emby Webhook (For Viewing Automation)

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+

  1. Go to http://your-episeerr:5002/setup
  2. Scroll to Emby section
  3. 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)
  4. Click Test Connection to verify
  5. 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:

  1. Emby โ†’ User Preferences (top-right avatar) โ†’ Notifications โ†’ Webhooks โ†’ Add Webhook
  2. Configure:
    • Webhook Name: Episeerr Episode Tracking
    • Webhook URL: http://your-episeerr:5002/api/integration/emby/webhook
    • Events: Enable "playback.start" and "playback.stop"
    • (Emby sends all media types; Episeerr routes Episodes to series rules and Movies to movie cleanup rules automatically)
  3. Save

Movie support: Emby's playback.stop events include Item.Type for both episodes and movies. Episeerr records movie watch timestamps on stop, which the cleanup scheduler uses for grace period logic. No additional configuration needed beyond ensuring playback.stop is enabled.

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:

EventWhat Episeerr Does
playback.startStarts polling /Sessions for this session every POLL_INTERVAL seconds
Poll hits TRIGGER_PERCENTAGEFires episode processing, marks session as handled
playback.stopStops 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"

4. Jellyseerr/Overseerr Webhook (Optional)

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:

  1. Jellyseerr/Overseerr โ†’ Settings โ†’ Notifications โ†’ Webhooks

  2. Add Webhook:

    • Webhook URL: http://your-episeerr:5002/api/integration/seerr/webhook
    • Notification Types: Enable "Request Approved"
  3. Save

image
[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"

How to Use

Create Your First Rule

Rules control what happens when you watch episodes.

  1. Open Episeerr: http://your-server:5002

  2. Go to Rules โ†’ Create New Rule

  3. Configure:

    SettingWhat It DoesExample
    NameRule identifier"binge_watcher"
    GETEpisodes to prepare"3 episodes" = next 3 ready
    KEEPEpisodes to retain"1 episode" = keep only last watched
    ActionMonitor or Search"Search" = actively download
  4. Optional Time-Based Cleanup:

    SettingWhat It DoesExample
    Grace WatchedDelete old watched episodes after X days"7 days"
    Grace UnwatchedDelete unwatched episodes after X days"14 days"
    DormantDelete everything after X days inactive"30 days"
  5. Mark as Default Rule (if this is your main rule)

  6. Save

[Rule creation form showing all fields]
[Example configuration for binge watcher]

Adding Series

There are three paths into Episeerr, each with different behavior.


Path 1: From outside Episeerr (Sonarr, Seerr, nzb360, etc.)

Episeerr intercepts series added to Sonarr via tags on the Sonarr webhook.

TagWhat happens
episeerr_<rulename>Processed immediately with that rule โ€” GET/monitor applied, tag removed
episeerr_defaultSame as above, but resolves to whichever rule is currently set as your default (Rules page) instead of a specific named rule โ€” otherwise behaves exactly like episeerr_<rulename>
episeerr_selectPending request created, delay profile holds all downloads until you confirm selections
(no tag, auto-assign on)Silently added to default rule, waits for first watch before doing anything

Only episeerr_select holds downloads via a Sonarr delay profile. episeerr_default and any specific episeerr_<rulename> tag are both ordinary rule tags with no such hold โ€” if Episeerr happens to be down at the exact moment you (or Seerr, or nzb360) apply one, nothing stops Sonarr's normal automatic search for however long Episeerr stays down. It's optional, but good practice to also add episeerr_delay alongside whichever rule tag you're using โ€” episeerr_default + episeerr_delay, or episeerr_<rulename> + episeerr_delay โ€” Episeerr removes episeerr_delay itself once it actually processes the series, whether that happens right away or later after a restart. See Episode Selection setup below for the delay profile itself.

Use cases: Add from Sonarr UI, request from Jellyseerr/Overseerr, add from nzb360


Path 2: Plex Watchlist Sync

  1. Enable Plex Watchlist Sync on the Setup page
  2. Add a show or movie to your Plex watchlist
  3. On the next sync cycle:
    • TV shows โ†’ added to Sonarr with episeerr_select tag โ†’ pending request created
    • Movies โ†’ sent directly to Radarr

Use case: Browse Plex Discover, add to watchlist, Episeerr handles the rest


Path 3: Search within Episeerr

  1. Use the search bar in Episeerr to find a TV show by name
  2. Click Add on the result
  3. The season/rule selection screen opens immediately โ€” nothing has been written to Sonarr yet
  4. Choose a rule and/or select specific seasons/episodes
  5. Click Apply Rule or Submit โ€” only then does Episeerr add the series to Sonarr and update your Plex watchlist
  6. Cancel at any point โ†’ nothing is added anywhere

No episeerr_select tag or delay profile needed โ€” Episeerr drives the whole flow directly.

Movies added via search go straight to Radarr with your default quality profile and root folder, and are added to your Plex watchlist immediately.


Existing series: manual assignment

For series already in Sonarr โ€” go to Episeerr โ†’ Series Management, select a series, choose a rule, and assign.


Episode Selection

Choose specific episodes manually across seasons โ€” or just pick a rule.

One-time Sonarr setup (required for Path 1 and Path 2 only):

Without this, external adds with episeerr_select will start downloading immediately before you make your selection.

  1. Sonarr โ†’ Settings โ†’ Profiles โ†’ Release Profiles โ†’ Add
  2. Configure:
    • Name: Episeerr Episode Selection Delay
    • Delay: 10519200 (20 years)
    • Tags: episeerr_select
  3. Save
image

Not needed for Path 3 (search within Episeerr) โ€” Sonarr isn't touched until after you confirm.

Episeerr manages this profile's tags itself going forward โ€” at every startup it makes sure episeerr_select and the internal episeerr_delay tag are attached to whichever profile you set up here, and only those two. Rule tags (episeerr_<rulename>, including episeerr_default) are deliberately left off, so ongoing downloads for a series Episeerr has already processed aren't delayed โ€” only series still waiting to be processed are held. Add episeerr_delay alongside a rule tag yourself when you want that protection for a specific add (see the tag table above).

Entering the selection flow:

HowEntry point
Search within Episeerr โ†’ AddGoes directly to selection screen
Plex watchlist sync (TV)Appears in Pending Items โ†’ click Select
Add to Sonarr with episeerr_select tagAppears in Pending Items โ†’ click Select
Any series already in SonarrClick the list icon on the poster or in Series Management

Selection Flow and Rule Picker

The season selection page shows a rule dropdown at the top regardless of how you arrived.

OptionWhat It Does
Apply RuleAssigns the rule โ€” rule's GET logic runs immediately, ongoing management from there
Select seasons/episodes manuallyPick exactly what to download; selected rule still handles ongoing management

Cancel โ†’ deletes the pending request, nothing is added to Sonarr or Plex watchlist.

The rule dropdown pre-selects the show's current rule if it already has one โ€” so re-routing a series is a one-click change.


Features Explained

๐Ÿ”„ Plex Watchlist Sync

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:

  1. Add show/movie to Plex watchlist
  2. Episeerr polls on your configured interval
  3. TV โ†’ pending selection request + episeerr_select tag in Sonarr
  4. Movie โ†’ sent directly to Radarr
  5. (Optional) Watched movies removed from Radarr after grace period

๐ŸŽฏ Episode Selection

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:

  1. Series enters selection flow (tag, watchlist sync, or series page icon)
  2. Season selection page appears with a rule picker at the top
  3. Either apply a rule directly (no manual picking needed), or choose specific episodes below
  4. Only selected episodes download; the chosen rule handles ongoing management

๐Ÿ“Œ Always Have (Rule Parameter)

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 overrides this, on purpose. (Keep Pilot, if enabled, still survives even Dormant โ€” leave it off if you want Dormant to remove a series completely.)

Base expressions:

ExpressionResult
s1e1Just the pilot
s1All of season 1
s1, s*e1Season 1 + first ep of every other season
s1-3Seasons 1 through 3
s1e1-5Season 1, episodes 1-5
allEverything

Modifiers (append to any base expression):

ExpressionBehaviour
s*e1Grab & permanently keep E1 of every season (default)
s*e1-Grab E1 of every season; follows grace/keep rules after watched
s*e1+Per-season activation gate โ€” get-count held at 0 until E1 playback starts
s*e1+-Per-season gate + E1 removable after activation
e1+Sequential: grab one season at a time, advance on finale
s1e1+Activation gate on pilot only; full auto from S2
pilot+Alias for e1+
  • + โ€” suppresses get-count until the activation episode starts playing (per-season state); fires on playback start across Plex, Jellyfin, Emby, and Tautulli
  • - โ€” activation episode is removable by grace/keep after activation fires

Combine with commas. Leave blank to skip. Always Have, Get, Keep, Grace, and Dormant are all independent โ€” use any combination.


โšก Viewing Automation

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:

  1. Watch S1E5
  2. Webhook fires to Episeerr
  3. Rule applied: GET next 2 episodes
  4. S1E6, S1E7 now monitored/searched
  5. KEEP rule: Delete S1E1-S1E4 (outside keep window)

Example flow:

Watch E5 โ†’ Get E6, E7 โ†’ Keep E5 โ†’ Delete E1-E4

๐Ÿ’พ Storage Management

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 TypeTriggerWhat It Does
Grace WatchedX days inactiveDeletes old watched episodes, keeps last as bookmark
Grace UnwatchedX days inactiveDeletes unwatched episodes, keeps first as bookmark
DormantX days inactiveDeletes everything from abandoned shows, bypassing Always Have (Keep Pilot, if enabled, still survives)

Storage Gate:

  • Set threshold: "Keep 20GB free"
  • When set, all three cleanup tiers (Dormant, Grace Watched, Grace Unwatched) process series oldest-inactivity-first and stop as soon as free space clears the threshold โ€” so cleanup only deletes as much as it actually needs
  • Without a threshold set, cleanup runs on day-count alone, same as always

Bookmarks:

  • Grace cleanup ALWAYS keeps at least 1 episode
  • You never lose your viewing position

Configuration Examples

Binge Watcher

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 days

What 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

Current Shows

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 days

What happens:

  • Watch E5 โ†’ E6 monitored
  • Keep E3, E4, E5
  • After 30 days โ†’ Cleanup old episodes
  • Unwatched episodes never auto-deleted

Protected Series

Profile: Never delete, keep everything

Rule Name: protected
GET: All
KEEP: All
Action: Search
Grace Watched: null
Grace Unwatched: null
Dormant: null

What happens:

  • Watch E5 โ†’ All future episodes monitored
  • Nothing ever deleted
  • Perfect for rewatchable favorites

Season Binger

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 days

What happens:

  • Watch S2E1 โ†’ All of S3 monitored
  • Keep all of S2, delete S1
  • After 14 days โ†’ Cleanup S2
  • Perfect for binging complete seasons

Showcase

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: null

What 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

One-at-a-Time with Pilot

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 days

What 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

Sequential Activation (one season at a time)

Profile: Grab only the current season; unlock the next when the finale is watched

Rule Name: sequential
Always Have: e1+
GET: 1 episode
KEEP: 1 episode
Action: Search

What happens:

  • Show added โ†’ S1E1 downloaded, get-count held at 0 until S1E1 starts playing
  • Press play on S1E1 โ†’ activation fires immediately, get-count unlocked, normal Get/Keep applies
  • Watch S1 finale โ†’ S2E1 grabbed, get-count held again until S2E1 starts playing
  • Ended series: does not advance past the final season

Troubleshooting

Container Won't Start

Check:

docker logs episeerr

Common 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)

Webhooks Not Working

Test webhook reception:

# Watch logs live
docker logs -f episeerr | grep webhook

# Check recent webhook events
docker logs episeerr | grep "Received.*webhook" | tail -20

Common issues:

ProblemCheckSolution
No webhooks receivedNetwork connectivityCan webhook sender reach Episeerr?
Webhooks received but nothing happensSeries assignmentIs series in a rule?
Wrong episodes managedWebhook dataCheck 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 /setup page to configure services with URLs and API keys (recommended), or use environment variables.


Episodes Not Monitoring

Check series assignment:

Episeerr โ†’ Series Management

Verify:

  1. Series is listed under a rule
  2. Rule has GET settings configured
  3. Watch an episode to trigger

Manual trigger:

# Watch an episode, then check logs
docker logs episeerr | grep "Monitored.*episodes"

Tags Not Working

For direct rule tags (episeerr_binge_watcher):

  1. Verify tag exists in Sonarr:

    • Sonarr โ†’ Settings โ†’ Tags
    • Tag must match rule name exactly
  2. Check Sonarr webhook:

    • Settings โ†’ Connect โ†’ Webhook
    • URL: http://episeerr:5002/sonarr-webhook
    • Trigger: "On Series Add" enabled
  3. Check logs:

    docker logs episeerr | grep "Processing.*with tag"
    

Jellyfin Not Working

Most common issue: Missing JELLYFIN_USER_ID

# REQUIRED for Jellyfin
- JELLYFIN_USER_ID=your_username  # This is your Jellyfin login name

Check 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"

Screenshots

image

Companion App (Optional)

EpiseerrApp is an Android client (phone or Android TV) for managing your Episeerr instance โ€” rules, pending approvals, and service config, without opening a browser. It's entirely optional: Episeerr runs fully without it.

  • Download: latest release, or grab the newest build from the repo's Actions tab after any push to main
  • Install: sideload on Android TV or mobile โ€” see the EpiseerrApp README for step-by-step instructions
  • Point it at your Episeerr server URL on first launch, same as any other client

FAQ

Plex Watchlist Sync

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.


General

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 overrides it, on purpose (Keep Pilot, if enabled, still survives). Add + to create an activation gate (get-count held until the episode is watched) or - to make the episode removable after it's watched.

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.


Tags & Assignment

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

Viewing Automation

Q: Episodes aren't updating when I watch?
A: Check:

  1. Is viewing webhook configured? (Tautulli or Jellyfin)
  2. Is series assigned to a rule?
  3. 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.


Deletions

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), always respects Always Have
  • Dormant: Deletes everything from completely abandoned shows, bypassing Always Have on purpose (Keep Pilot, if enabled, still survives)

Jellyfin Specific

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.


Storage Management

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.


Support

Why donations matter: most of the fast turnaround on bug reports right now โ€” same-day diagnosis, same-day fixes, same-day releases โ€” is only possible because of a paid AI coding subscription I use for development. That subscription is funded by donations. If donations don't cover it and I don't renew it, that pace stops โ€” I'll still be around, but fixes and responses will get a lot slower, and at worst support is basically closed. If Episeerr is useful to you, buying a coffee is what keeps this pace going.

Get Help

Logs Location

# Docker
docker logs episeerr

# Logs directory
./logs/app.log

# Live monitoring
docker logs -f episeerr

Common Log Searches

# 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"

Contributing

Contributions welcome! Please open an issue or pull request on GitHub.


License

GNU Affero General Public License v3.0 โ€” modified versions, including ones run as a hosted/network service, must make their source available under the same license.


Acknowledgments

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 โฌ†๏ธ