Sortarr is a lightweight web dashboard for Sonarr and Radarr that helps you understand how your media library uses storage. It is not a Plex tool, but it is useful in Plex setups for spotting oversized series or movies and comparing quality vs. size trade-offs.
Core functionality is complete. Current work focuses on performance improvements and expanding analysis views.
- Runs Sonarr and/or Radarr and wants deeper storage insight
- Cares about balancing quality and disk space
- Manages large or growing libraries and wants to spot inefficiencies early
- Prefers analysis and visibility over automation
Sortarr connects to the Sonarr and Radarr APIs, computes size and efficiency metrics, caches results for performance, and serves a small read-only UI and HTTP API. It does not modify files or take any actions against your media.
- Sonarr series size stats (total includes specials; averages exclude specials)
- Expand Sonarr series rows to view season rollups and episode lists (virtualized for large seasons)
- Radarr movie size stats (file size and GiB per hour)
- Radarr bitrate metric with estimated fallback indicator
- Sorting, filtering, and column toggles
- Filter builder dropdowns with active filter bubbles (quick chips optional)
- Optional Arr metadata columns (status, monitored, quality profile, tags, release group)
- Sonarr series metadata and wanted columns (series type, original language, genres, last aired, missing/cutoff counts)
- Sonarr chips for missing, cutoff unmet, recently grabbed, scene numbering, and airing
- Fixed-width Title column with CSS ellipsis to keep large tables stable
- CSV export for Sonarr and Radarr (playback columns appear only when a playback provider is configured)
- Date added column for Sonarr and Radarr views
- Compressed JSON/CSV API responses for large payloads
- Multiple Sonarr/Radarr instances with optional friendly names
- Optional basic auth and configurable cache
- Optional Jellystat or Tautulli playback stats (play count, last watched, watch time, watch vs content hours, users)
- Playback match status orbs beside titles to flag potential mismatches
- Status pill distinguishes receiving playback data vs matching during refresh
- Refresh all Sonarr/Radarr items from the status bar and per-row refresh actions that reload the refreshed item after a short delay (playback refreshes when configured; per-item playback refreshes only for Tautulli)
- Defer playback overlay on cold starts and backfill in the background with an in-app progress notice (live processed counts + last update time)
- Audio/subtitle language columns with filters and quick chips
docker compose up -dThe default docker-compose.yaml pulls ghcr.io/jaredharper1/sortarr:latest (release builds). To use Docker Hub instead, set image: docker.io/jaredharper1/sortarr:latest.
Images are published for linux/amd64 and linux/arm64/v8 on both registries, so Apple Silicon pulls work without changes. To force an architecture, set platform in docker-compose.yaml (e.g., linux/arm64/v8 or linux/amd64).
Open http://<host>:9595. The first visit redirects to /setup, where you can enter Sonarr/Radarr URLs and API keys. The setup page writes a .env file at ENV_FILE_PATH (defaults to ./data/Sortarr.env in docker-compose.yaml). URLs can be entered with or without a scheme; duplicate schemes are normalized. Additional instances are under the Advanced sections, and instance names surface in the Instance column/chips when configured.
The first load after startup can take a while for large libraries (especially with Tautulli); later loads are cached and faster.
The Docker image runs Gunicorn with a single worker and 4 threads to keep refresh state and cache progress consistent. If you need to change worker or thread counts, override the container command (and be aware that multiple workers require shared state).
An Unraid template is provided at docs/unraid-template.xml.
Support: until a forum thread exists, use GitHub Issues for Unraid support.
- Copy it to
/boot/config/plugins/dockerMan/templates-user/(for example,sortarr.xml). - Add the container in Unraid and set the WebUI port (default 9595) and appdata path.
- Start the container and open the WebUI from the Docker page.
Sortarr writes and reads:
SONARR_URLSONARR_API_KEYSONARR_NAME(optional unless additional Sonarr instances are configured)SONARR_URL_2SONARR_API_KEY_2SONARR_NAME_2SONARR_URL_3SONARR_API_KEY_3SONARR_NAME_3SONARR_EPISODEFILE_WORKERS(optional, defaults to8; parallel episode file fetch workers for Sonarr)SONARR_EPISODEFILE_MODE(optional,seriesdefault;bulkfetches all episode files at once,fastskips per-episode file lookups and omits codec/language detail)SONARR_EPISODEFILE_DELAY_SECONDS(optional, per-request delay for Sonarr episode file calls; default0)SONARR_EPISODEFILE_CACHE_SECONDS(optional, per-series episode file cache TTL; default600, set0to disable)RADARR_URLRADARR_API_KEYRADARR_NAME(optional unless additional Radarr instances are configured)RADARR_URL_2RADARR_API_KEY_2RADARR_NAME_2RADARR_URL_3RADARR_API_KEY_3RADARR_NAME_3RADARR_WANTED_WORKERS(optional, defaults to2; parallel Radarr wanted/missing/cutoff/recent fetch workers, set1for low-end boxes)RADARR_INSTANCE_WORKERS(optional, defaults to1; parallel Radarr instance fetch workers, opt-in for multi-instance setups)TAUTULLI_URL(optional)TAUTULLI_API_KEY(optional)TAUTULLI_METADATA_CACHE(optional, defaults to./data/Sortarr.tautulli_cache.json)TAUTULLI_METADATA_LOOKUP_LIMIT(optional, defaults to-1for no limit; set to0to disable metadata matching)TAUTULLI_METADATA_LOOKUP_SECONDS(optional, defaults to0for no lookup time limit)TAUTULLI_METADATA_WORKERS(optional, defaults to4; parallel metadata lookup workers)TAUTULLI_METADATA_SAVE_EVERY(optional, defaults to250; persist metadata cache every N lookups)TAUTULLI_REFRESH_STALE_SECONDS(optional, defaults to3600; clear stuck Tautulli refresh locks)TAUTULLI_TIMEOUT_SECONDS(optional, defaults to60; per-request timeout for each Tautulli API call,0falls back to 45 seconds)TAUTULLI_FETCH_SECONDS(optional; total fetch budget per load, defaults to0for no limit)SONARR_TIMEOUT_SECONDS(optional, defaults to90; per-request timeout for Sonarr API calls,0disables the timeout)RADARR_TIMEOUT_SECONDS(optional, defaults to90; per-request timeout for Radarr API calls,0disables the timeout)SONARR_CACHE_PATH(optional, defaults to./data/Sortarr.sonarr_cache.json)RADARR_CACHE_PATH(optional, defaults to./data/Sortarr.radarr_cache.json)PUID(optional, container user ID; used when set alongsidePGID)PGID(optional, container group ID; used when set alongsidePUID)BASIC_AUTH_USERBASIC_AUTH_PASS
NOTE
Reverse proxy / HTTPS (Traefik, Nginx, Cloudflare, etc.)
Sortarr can be run behind a reverse proxy. In that case it may need to trust X-Forwarded-* headers so Flask correctly detects the external scheme/host (for example https://sortarr.mydomain.com). If this is not set correctly, you may see errors like "CSRF origin mismatch" during setup when accessing Sortarr via HTTPS through a proxy.
SORTARR_PROXY_HOPS(optional)
Typical values:
0= Disabled. Safer if you only run Sortarr locally and are not using a reverse proxy.1= One proxy hop (Traefik/Nginx directly in front of Sortarr). Default.2= Two proxy hops (for example Cloudflare Tunnel -> Traefik -> Sortarr).
Security note:
- If
SORTARR_PROXY_HOPSis enabled, make sure Sortarr is only reachable through your reverse proxy (for example, do not publish the Sortarr container port directly to the internet).
Runtime overrides (optional):
ENV_FILE_PATH(optional, defaults to.envnext toapp.py; docker-compose sets/data/Sortarr.env)PORT(optional, defaults to8787; Docker maps9595to8787by default)SORTARR_LOG_LEVEL(optional, defaults toINFO)SORTARR_STRICT_INSTANCE_ERRORS(optional, defaultfalse; fail requests if any instance is unavailable)SORTARR_DEFER_WANTED(optional, defaulttrue; defer wanted/missing/cutoff/recent counts on cold start and backfill in the background)SORTARR_RADARR_LITE_FIRST(optional, defaulttrue; serve a lite Radarr payload on cold start, then hydrate full details in the background)ARR_BACKOFF_BASE_SECONDS(optional, base delay for retries on 429/5xx; default0.5)ARR_BACKOFF_MAX_RETRIES(optional, retry attempts for 429/5xx or request errors; default2)ARR_CIRCUIT_FAIL_THRESHOLD(optional, failures before opening the circuit; default3)ARR_CIRCUIT_OPEN_SECONDS(optional, cooldown before retrying once the circuit opens; default30)
Requirements and notes:
- Static assets are cache-busted using the app version, so UI updates should load immediately after upgrades.
- SortarrReset UI clears local UI settings and reloads using the cached data. Data status panel actions:
- Refresh Sonarr/Radarr data reloads only the active tab's data and updates the persistent cache.
- Refresh playback data reloads both Sonarr and Radarr (and playback if configured) and updates the persistent cache, then rebuilds playback matching using cached provider data (Tautulli) or starts a playback refresh (Jellystat).
- Clear caches & rebuild clears all cache files and starts a full rebuild, similar to a cold start.
- Sonarr/Radarr API keys are required (read-only keys recommended)
- Tautulli is optional and only used for playback stats (playback columns/filters/chips stay hidden until configured)
- Tautulli metadata lookups are cached to disk for faster matching; adjust lookup limits/workers and save cadence as needed
- Setting
TAUTULLI_METADATA_LOOKUP_LIMIT=0disables metadata matching - Stale Tautulli refresh locks clear after
TAUTULLI_REFRESH_STALE_SECONDSto avoid stuck matching - On first run after an upgrade, Sortarr clears caches and drops legacy Tautulli default env values so new defaults apply
- When Tautulli matching runs in the background, the UI auto-refreshes to apply playback stats
- Arr caches persist until Refresh all data is clicked
- When
PUID/PGIDare set, the container runs as that user and will chown the config/cache paths on startup. - Treat
Sortarr.envas a secret; it stores API keys and optional basic auth credentials - Basic auth is optional but recommended if exposed beyond your LAN
- State-changing API requests require a CSRF token header (
X-CSRF-Token) that matches thesortarr_csrfcookie; the UI handles this automatically - Designed for on-demand queries with persistent caching; the UI only polls while Tautulli matching finishes
- No database or media filesystem access required
If a title shows missing or incorrect Tautulli stats, the fastest fix is to refresh the item inside Tautulli after you confirm it is matched correctly in Arr and Plex.
Steps:
- In Sonarr/Radarr, confirm the title/year and IDs are correct for the item.
- In Plex, confirm the item metadata (title/year/IDs) matches Sonarr/Radarr.
- In Plex, play the item for 30-60 seconds (this creates fresh Tautulli history).
- In Tautulli, open the Now Playing entry and confirm it is the correct title.
- In Tautulli, go to the matching library and search for the item by name.
- If it is missing or stale, open the item and choose Media Info -> Refresh Media Info.
- After the refresh completes, search again to confirm the item appears.
- Back in Sortarr, click Refresh all data and wait for the load to complete.
If the title still does not match, the issue is likely missing IDs in Plex/Tautulli or a mismatch between libraries. Share the title and the Tautulli history count for that item so we can investigate further.
Sortarr reads totals from the Tautulli History API (full play history). The Tautulli UI can show different totals because:
- The show page "Global Stats" totals are built from the Tautulli metadata cache, which can be incomplete for large/old libraries or after lookup limits.
- The History page footer shows the total for the current page of results, not the full filtered set.
If the show page totals look low, rebuild Tautulli's metadata cache:
- Stop Tautulli.
- In Tautulli, open Settings and note the "Cache Directory: X" path, then back up that directory before clearing it.
- Start Tautulli.
- Refresh the library in Tautulli and wait for metadata to rebuild.
After the rebuild, the show page totals should align with History totals (and with Sortarr).
Use the filter builder row to add Category/Condition/Value filters and build chips; active filters appear as bubbles and can be removed with a click. Use the Chips button to switch back to the classic Title/Path + Advanced filters and quick chips.
Use field:value for wildcards and comparisons. Numeric fields treat field:value as >= (use = for exact). gbperhour and totalsize with integer values use whole-number buckets (e.g., gbperhour:1 matches 1.0-1.99).
Examples: audio:Atmos audiocodec:eac3 audiolang:eng sublang:eng nosubs:true studio:pixar seriestype:anime missing:true cutoff:true airing:true isavailable:true playcount>=5 neverwatched:true mismatch:true dayssincewatched>=365 watchtime>=10 contenthours>=10 gbperhour:1 totalsize:10 videocodec:x265 videohdr:hdr
Fields: title, titleslug, tmdbid, path, rootfolder, instance, status, monitored, qualityprofile, tags, releasegroup, studio, seriestype, originallanguage, genres, lastaired, hasfile, isavailable, incinemas, lastsearchtime, edition, customformats, customformatscore, qualitycutoffnotmet, languages, videoquality, videocodec, videohdr, resolution, audio, audiocodec, audiocodecmixed, audiochannels, audiolang, audiolanguagesmixed, sublang, subtitlelanguagesmixed, nosubs, missing, cutoff, cutoffunmet, recentlygrabbed, scene, airing, matchstatus, mismatch, playcount, lastwatched, dayssincewatched, watchtime, contenthours, watchratio, users, episodes, seasons, totalsize, avgepisode, runtime, filesize, gbperhour, bitrate.
Match status values: matched, unmatched, skipped, unavailable, plus future, nodisk, and notchecked for skipped reasons.
Note: Resolution filters match by height with tolerance (e.g., 1920x1036p matches 1080p), treat 1920x* as 1080p and 1280x* as 720p when dimensions are present, and accept aliases like 4k/uhd/fhd/hd/sd. The UI chips now use videoquality: for more reliable matches.
Note: Language lists are shortened in the table; use "Show all" to expand them.
/api/shows/api/movies/api/shows.csv/api/movies.csvPOST /api/sonarr/refresh(optionalseriesId,instance_id)POST /api/radarr/refresh(optionalmovieId,instance_id)POST /api/tautulli/refresh_item(rating_key or section_id)- Error responses from Arr fetches may include a sanitized
X-Sortarr-Errorheader.
- Replace Sonarr or Radarr, or duplicate their core functionality
- Modify, manage, or automate media files
- Act as a downloader, indexer, or media manager
- Provide real-time, event-driven monitoring
- Expanded efficiency views and comparisons
- Performance improvements for very large libraries
- More grouping and filtering options
Feedback and ideas are welcome.
Press Ctrl+Shift+P to toggle a lightweight perf overlay (FPS estimate, long task count when supported, render time, visible rows, and DOM counts).
You can render synthetic datasets without connecting Sonarr/Radarr:
?bench=1&app=radarr&rows=1000?bench=1&app=radarr&rows=5000?bench=1&app=radarr&rows=20000- Add
&wide=1to show more columns.
Sortarr can display small poster previews streamed directly from Sonarr and Radarr:
- Sonarr: series poster appears only inside the expanded series panel header.
- Radarr: movie poster appears as a hover tooltip (table row heights do not change).
Disable all image loading with ?images=0.

