Theater

Jellyfin, the arr apps, downloads, music and books in one place

palupdated 2026-09-29ProductivityIn pal's registry󱔓Menu bar: Downloads, Now Playing, Requests, Arr queue󰌹7 deep links

Open in pal

In pal's registry: the button shows it in pal, which asks before installing; the command does the same from a terminal. This page can't see what your pal has installed.

Theater: Every service of the home media stack on one list: its version, what it is doing, and what needs a hand
Every service of the home media stack on one list: its version, what it is doing, and what needs a hand
Theater: Jellyfin: what you are halfway through and what just arrived, how far in and the rating; cmd+Enter plays it on the TV
Jellyfin: what you are halfway through and what just arrived, how far in and the rating; cmd+Enter plays it on the TV
Theater: Requests: the pending ones first with who asked, 4K and where each one stands; approve or decline from the row
Requests: the pending ones first with who asked, 4K and where each one stands; approve or decline from the row
Theater: Sonarr: its commands, its health, an import stuck in the queue with the reason, and what airs this week
Sonarr: its commands, its health, an import stuck in the queue with the reason, and what airs this week
Theater: SABnzbd and qBittorrent in one list: speed, time left and size; pause everything or set a limit
SABnzbd and qBittorrent in one list: speed, time left and size; pause everything or set a limit
Theater: One search across every Prowlarr indexer, grouped by indexer with size, age and grabs; Enter sends it to the downloader
One search across every Prowlarr indexer, grouped by indexer with size, age and grabs; Enter sends it to the downloader
1 of 6

What it does

Your self-hosted media stack in one place: Jellyfin, Jellyseerr, Radarr, Sonarr, Lidarr, Prowlarr, Bazarr, SABnzbd, qBittorrent, Navidrome, Audiobookshelf, Kavita and more. Fill in the services you run and only those appear.

  • Every service you set up as a row with its health; enter opens its web page, cmd+enter its list
  • Jellyfin: Continue Watching and Latest with posters, search, Now Playing, play on a device
  • Jellyseerr requests to approve or decline, and new requests from a title search
  • Radarr, Sonarr and Lidarr: queue, calendar, wanted and history; add a title with the default profile
  • SABnzbd and qBittorrent as one downloads queue: pause, resume, delete, limit the speed
  • Bar items for download speed, who is watching, pending requests and the arr queue, hidden when idle

Palettes 33

  • Theatertheaterliverefreshes every 30s

    Every configured service as a row with live health (up or down, version, one key number); Enter opens its web UI, cmd+Enter its palette in pal. A service you have not set up is one Set up row.

    • ↵Open the service's web UI
    • ⌘↵The service in pal
    • ⌘CCopy the URL
    • ⌘RCheck again
  • Jellyfinjellyfinliverefreshes every 1 min

    Continue Watching with the position, then the latest additions, posters on the rows; the pane has the overview, year, rating, runtime and watched state.

    • ↵Open in Jellyfin
    • ⌘↵Play on a device (a picker of the signed-in sessions)
    • ⌘⇧PMark played or unplayed
    • ⌘FAdd to or remove from favourites
    • ⌘CCopy the link
  • Search Jellyfinjellyfin-searchinputfallback row: Search Jellyfin for “…”

    Movies, series, episodes, albums and songs by title, 300 ms after the last key; the same actions as the Jellyfin rows.

    • ↵Open in Jellyfin
    • ⌘↵Play on a device
    • ⌘⇧PMark played or unplayed
    • ⌘FFavourite
    • ⌘CCopy the link
  • Now Playing on Jellyfinjellyfin-playingliverefreshes every 30s

    Active sessions: who watches what on which device, with the position; Enter opens the item, cmd+Enter pauses or resumes that session.

    • ↵Open the item in Jellyfin
    • ⌘↵Pause or resume the session
    • ⌘⇧SStop playback
  • Play onjellyfin-playlist

    The picker Play on a device opens: every Jellyfin session that takes remote control; Enter plays the item there.

    • ↵Play the item on that session
  • Requestsseerr-requestsliverefreshes every 1 min

    Jellyseerr requests, pending first, with the poster, who asked and the status; Approve and Decline on a pending one. The filter narrows to pending, approved, available or all.

    • ↵Open in Jellyseerr
    • ⌘↵Approve (a pending request)
    • ⌘⇧DDecline (asks first)
    • ⌘CCopy the link
  • Request a titleseerr-requestinputfallback row: Request “…” on Jellyseerr

    Search TMDB through Jellyseerr; Enter requests the movie or series (asks first), cmd+Enter in 4K when the server allows it. A title already available or requested is tagged.

    • ↵Request (asks first)
    • ⌘↵Request in 4K, when enabled
    • ⌘OOpen in Jellyseerr
    • ⌘CCopy the TMDB link
  • Radarrradarrliverefreshes every 1 min

    The queue with progress and status, the next seven days, health warnings, and the commands: add a movie, wanted, history, sync the watchlist now, open Radarr.

    • ↵Open in Radarr
    • ⌘⌫Remove from the queue (asks first)
    • ⌘CCopy the title
  • Add a movieradarr-addinputfallback row: Add “…” to Radarr

    Look a movie up on Radarr; Enter adds it with the default quality profile and root folder and searches (asks first). One already in the library is tagged.

    • ↵Add and search (asks first)
    • ⌘↵Add without searching
    • ⌘OOpen on TMDB
  • Wanted moviesradarr-wantedlistrefreshes every 5 min

    Monitored movies without a file, newest release first; Enter searches for one, cmd+Enter opens it.

    • ↵Search for the movie now
    • ⌘↵Open in Radarr
  • Radarr historyradarr-historylistrefreshes every 5 min

    What Radarr grabbed, imported or failed on lately.

    • ↵Open the movie in Radarr
  • Sonarrsonarrliverefreshes every 1 min

    The queue with progress and status, the episodes airing in the next seven days, health warnings, and the commands: add a series, wanted, history, sync the watchlist now, open Sonarr.

    • ↵Open in Sonarr
    • ⌘⌫Remove from the queue (asks first)
    • ⌘CCopy the title
  • Add a seriessonarr-addinputfallback row: Add “…” to Sonarr

    Look a series up on Sonarr; Enter adds it with the default quality profile and root folder, every season monitored, and searches (asks first).

    • ↵Add and search (asks first)
    • ⌘↵Add without searching
    • ⌘OOpen on TVDB
  • Wanted episodessonarr-wantedlistrefreshes every 5 min

    Monitored episodes that aired without a file, newest first; Enter searches for one.

    • ↵Search for the episode now
    • ⌘↵Open the series in Sonarr
  • Sonarr historysonarr-historylistrefreshes every 5 min

    What Sonarr grabbed, imported or failed on lately.

    • ↵Open the series in Sonarr
  • Lidarrlidarrliverefreshes every 1 min

    The queue with progress and status, albums out in the next seven days, health warnings, and the commands: add an artist, wanted, history, open Lidarr.

    • ↵Open in Lidarr
    • ⌘⌫Remove from the queue (asks first)
    • ⌘CCopy the title
  • Add an artistlidarr-addinputfallback row: Add “…” to Lidarr

    Look an artist up on Lidarr; Enter adds them with the default profiles and root folder and searches for missing albums (asks first).

    • ↵Add and search (asks first)
    • ⌘↵Add without searching
    • ⌘OOpen on MusicBrainz
  • Wanted albumslidarr-wantedlistrefreshes every 5 min

    Monitored albums without files, newest first; Enter searches for one.

    • ↵Search for the album now
    • ⌘↵Open the artist in Lidarr
  • Lidarr historylidarr-historylistrefreshes every 5 min

    What Lidarr grabbed, imported or failed on lately.

    • ↵Open the artist in Lidarr
  • Downloadsdownloadsliverefreshes every 20s

    SABnzbd and qBittorrent as one queue: each item with progress, speed, time left and category; pause, resume and delete per item; Pause all, Resume all and a speed limit on top.

    • ↵Open the download client
    • ⌘↵Pause or resume the item
    • ⌘⌫Delete the item (asks first)
    • ⌘CCopy the name
  • Download historydownloads-historylistrefreshes every 2 min

    What SABnzbd and qBittorrent finished lately, a failed one with its reason.

    • ↵Open the download client
    • ⌘⌫Remove from the history (asks first)
  • Indexersprowlarrlistrefreshes every 5 min

    Every Prowlarr indexer with protocol, priority and health (a disabled or failing one says why), Prowlarr's own health warnings, and NZBHydra2's indexers when configured.

    • ↵Open the indexer in Prowlarr
    • ⌘↵Test the indexer
  • Search indexersprowlarr-searchinputfallback row: Search the indexers for “…”

    A release search across every Prowlarr indexer: title, size, age, seeders or grabs, the indexer as a tag; Enter grabs it through Prowlarr into the download client (asks first).

    • ↵Grab through Prowlarr (asks first)
    • ⌘↵Open the release page
    • ⌘CCopy the download link
  • Search NZBHydra2hydra-searchinputfallback row: Search NZBHydra2 for “…”

    A usenet search through NZBHydra2: title, size, age, grabs, the indexer as a tag; Enter sends the NZB to SABnzbd when it is configured, else opens Hydra.

    • ↵Send to SABnzbd (asks first), or open in Hydra
    • ⌘↵Open the release's page
    • ⌘CCopy the NZB link
  • Navidromenavidromeliverefreshes every 1 min

    What is playing now, then the albums added lately with their covers; Enter opens in Navidrome, cmd+S stars.

    • ↵Open in Navidrome
    • ⌘SStar or unstar
    • ⌘CCopy the link
  • Search Navidromenavidrome-searchinputfallback row: Search Navidrome for “…”

    Artists, albums and songs with cover art, 300 ms after the last key.

    • ↵Open in Navidrome
    • ⌘SStar or unstar
    • ⌘CCopy the link
  • Audiobookshelfabsliverefreshes every 1 min

    Continue listening with the progress, then what was added lately across your libraries; Enter opens in Audiobookshelf.

    • ↵Open in Audiobookshelf
    • ⌘⇧PMark finished
    • ⌘CCopy the link
  • Search Audiobookshelfabs-searchinputfallback row: Search Audiobookshelf for “…”

    Books, podcasts and authors across every library.

    • ↵Open in Audiobookshelf
    • ⌘CCopy the link
  • Kavitakavitaliverefreshes every 1 min

    Continue reading with the pages read, then the series added lately; Enter opens the web reader.

    • ↵Open in Kavita
    • ⌘CCopy the link
  • Search Kavitakavita-searchinputfallback row: Search Kavita for “…”

    Series, collections and files across your libraries.

    • ↵Open in Kavita
    • ⌘CCopy the link
  • Request a bookshelfmarkinputfallback row: Request the book “…”

    Search Shelfmark's book provider; Enter lists the releases found for the book, and Enter on one downloads it into the ebook library (asks first). With nothing typed, what Shelfmark is downloading now.

    • ↵The releases for the book
    • ⌘↵Open Shelfmark
  • Releasesshelfmark-releaseslist

    The releases Shelfmark found for one book (opened from Request a book): format, size, source, seeders; Enter downloads (asks first).

    • ↵Download the release (asks first)
    • ⌘CCopy the release title
  • Subtitlesbazarrlistrefreshes every 5 min

    Bazarr: how many movies and episodes still want subtitles, each provider's health, and the wanted items with the missing languages; Search missing subtitles runs Bazarr's task.

    • ↵Open in Bazarr
    • ⌘↵Search subtitles for the item

Settings

Extension, [extensions.theater]

jellyfin_url
empty text
jellyfin_key
empty secret

Dashboard › API Keys on the server.

jellyfin_user
empty text

Whose Continue Watching and watched state; the first user when empty.

seerr_url
empty text
seerr_key
empty secret

Settings › General on Jellyseerr (Overseerr works the same).

radarr_url
empty text
radarr_key
empty secret

Settings › General › Security.

sonarr_url
empty text
sonarr_key
empty secret
lidarr_url
empty text
lidarr_key
empty secret
prowlarr_url
empty text
prowlarr_key
empty secret
hydra_url
empty text
hydra_key
empty secret

Config › Main › API key.

bazarr_url
empty text
bazarr_key
empty secret

Settings › General › Security.

sab_url
empty text
sab_key
empty secret

Config › General › API key (the NZB key is not enough for the queue).

qbit_url
empty text
qbit_key
empty secret

qBittorrent 5.2+: a bearer key (POST /api/v2/app/rotateAPIKey from a logged-in session). Leave empty to sign in with the WebUI user and password instead.

qbit_user
empty text
qbit_password
empty secret
navidrome_url
empty text
navidrome_user
empty text
navidrome_password
empty secret

Sent as a Subsonic salted token, never in clear.

abs_url
empty text
abs_user
empty text
abs_password
empty secret
kavita_url
empty text
kavita_user
empty text
kavita_password
empty secret
shelfmark_url
empty text

The book request door (no auth).

filebrowser_url
empty text

Opened from the Theater palette.

homepage_url
empty text

The dashboard; opened from the Theater palette.

Defaults shown. Change them in pal's settings window or in the config file.

The README

Everything Theater does, key by key, from the extension's own README.md

Theater

Your home media stack in one extension: Jellyfin, Jellyseerr, Radarr, Sonarr, Lidarr, Prowlarr, NZBHydra2, Bazarr, SABnzbd, qBittorrent, Navidrome, Audiobookshelf, Kavita, Shelfmark, Filebrowser and Homepage. The Theater palette shows every configured service with live health and opens it; each service has its own palettes (what to continue, what is queued, a search, the requests, the wanted items) with the actions the service's own UI would offer (play, approve, add, pause, grab, star); four bar items say what is downloading, who is watching, what waits for an approval and what the arrs are fetching, each hidden while it has nothing to say. Only the services you fill in light up.

palette id kind what Enter does
Theater theater live, 30 s opens the service's web UI
Jellyfin theater-jellyfin live, 60 s opens the item in Jellyfin
Search Jellyfin theater-jellyfin-search input opens the item in Jellyfin
Now Playing on Jellyfin theater-jellyfin-playing live, 30 s opens the item
Play on theater-jellyfin-play pushed from Play on a device plays the item on that session
Requests theater-seerr-requests live, 60 s opens the request in Jellyseerr
Request a title theater-seerr-request input requests it (asks first)
Radarr, Sonarr, Lidarr theater-radarr, theater-sonarr, theater-lidarr live, 60 s opens in the app
Add a movie, series, artist theater-<app>-add input adds with the default profile and searches (asks first)
Wanted theater-<app>-wanted indexed, 5 min searches for it now
History theater-<app>-history indexed, 5 min opens in the app
Downloads theater-downloads live, 20 s opens the download client
Download history theater-downloads-history indexed, 2 min opens the download client
Indexers theater-prowlarr indexed, 5 min opens Prowlarr's indexers
Search indexers theater-prowlarr-search input grabs through Prowlarr (asks first)
Search NZBHydra2 theater-hydra-search input sends the NZB to SABnzbd (asks first)
Navidrome theater-navidrome live, 60 s opens in Navidrome
Search Navidrome theater-navidrome-search input opens in Navidrome
Audiobookshelf theater-abs live, 60 s opens in Audiobookshelf
Search Audiobookshelf theater-abs-search input opens in Audiobookshelf
Kavita theater-kavita live, 60 s opens the web reader
Search Kavita theater-kavita-search input opens the web reader
Request a book theater-shelfmark input lists the releases for the book
Releases theater-shelfmark-releases pushed from Request a book downloads the release (asks first)
Subtitles theater-bazarr indexed, 5 min opens in Bazarr

Marked rows (cmd-click, shift-click, shift+arrows) work together wherever one action suits several: queue items removed, wanted items searched in one command, lookup hits added, downloads paused, resumed or deleted, history removed, requests approved or declined, results requested, releases grabbed or sent, indexers tested, Jellyfin items marked played or favourite, Navidrome rows starred, Audiobookshelf items finished, subtitles searched, and the links and titles copied one per line. The downloads, requests and queue popovers mark their rows too.

Setting up

Everything is under Settings › Extensions › Theater, one group per service. A service with an empty URL (or an empty key, user or password) is not an error: the Theater palette shows one "Set up X" row whose Enter opens Settings on the field, its indexed palettes list nothing, and its search palettes say what to set.

service settings how it signs in where the key is
Jellyfin jellyfin_url, jellyfin_key, jellyfin_user X-Emby-Token Dashboard › API Keys; jellyfin_user picks whose watched state the rows show (the first administrator otherwise)
Jellyseerr seerr_url, seerr_key X-Api-Key Settings › General (Overseerr works the same)
Radarr, Sonarr, Lidarr <app>_url, <app>_key X-Api-Key Settings › General › Security
Prowlarr prowlarr_url, prowlarr_key X-Api-Key Settings › General › Security
NZBHydra2 hydra_url, hydra_key apikey query Config › Main
Bazarr bazarr_url, bazarr_key X-Api-Key Settings › General › Security
SABnzbd sab_url, sab_key apikey query Config › General (the full API key, not the NZB key)
qBittorrent qbit_url, then qbit_key or qbit_user + qbit_password a bearer key (5.2+), else a cookie from /auth/login with a Referer (qBittorrent's CSRF guard reports a wrong password without one) POST /api/v2/app/rotateAPIKey from a logged-in session
Navidrome navidrome_url, navidrome_user, navidrome_password Subsonic salted token (t = md5(password + salt)) the account
Audiobookshelf abs_url, abs_user, abs_password POST /login, a bearer JWT the account
Kavita kavita_url, kavita_user, kavita_password POST /api/Account/login, a bearer JWT; the user's API key signs cover urls the account
Shelfmark shelfmark_url none
Filebrowser, Homepage filebrowser_url, homepage_url none (open rows)

Keys and passwords are secret settings (the OS keychain). For the tests and a scratch run, PAL_THEATER_<SERVICE>_<URL|KEY|USER|PASSWORD> (PAL_THEATER_JELLYFIN_URL, PAL_THEATER_QBIT_PASSWORD) replaces any setting.

The endpoints each service is read and written through

service reads writes
Jellyfin /System/Info/Public, /Users, /Sessions?activeWithinSeconds=960, /UserItems/Resume, /Items/Latest, /Items?searchTerm=, /Items/{id}; posters /Items/{id}/Images/Primary (no key needed) POST/DELETE /UserPlayedItems/{id}, POST/DELETE /UserFavoriteItems/{id}, POST /Sessions/{id}/Playing?playCommand=PlayNow&itemIds=, POST /Sessions/{id}/Playing/PlayPause, .../Stop
Jellyseerr /api/v1/status, /settings/public (4K flags), /request/count, /request?filter=&take=&sort=added, /movie/{tmdb}, /tv/{tmdb}, /search?query=; posters from image.tmdb.org POST /request/{id}/approve, .../decline, POST /request { mediaType, mediaId, is4k, seasons }
Radarr, Sonarr (/api/v3), Lidarr (/api/v1) /system/status, /health, /queue, /calendar?start&end, /wanted/missing, /history, /importlist, /qualityprofile, /rootfolder, /metadataprofile, /{movie,series,artist}/lookup?term= DELETE /queue/{id}?removeFromClient=true&blocklist=false, POST /command (ImportListSync with definitionId, MoviesSearch, EpisodeSearch, AlbumSearch), POST /movie, /series, /artist
Prowlarr /api/v1/system/status, /indexer, /indexerstatus, /health, /search?query=&limit= POST /search { guid, indexerId } (a grab), POST /indexer/{id}/test
NZBHydra2 /api?t=caps, /api?t=search&q=&o=json none (the NZB link goes to SABnzbd's addurl)
Bazarr /api/system/status, /movies/wanted, /episodes/wanted, /providers POST /system/tasks?taskid=wanted_search_missing_subtitles_{movies,series}, PATCH /movies/subtitles?radarrid=, PATCH /episodes/subtitles?sonarrepisodeid=
SABnzbd /api?mode=version, queue, history mode=pause, resume, `queue&name=pause
qBittorrent /api/v2/app/version, /torrents/info, /transfer/info /torrents/stop and start (5.x; pause/resume before), /torrents/delete, /transfer/setDownloadLimit, /torrents/add
Navidrome ping, getNowPlaying, getAlbumList2?type=newest, search3, getCoverArt star, unstar
Audiobookshelf /status, /api/libraries, /api/me, /api/me/items-in-progress, /api/libraries/{id}/items?sort=addedAt, /api/libraries/{id}/search?q=, covers /api/items/{id}/cover?token= PATCH /api/me/progress/{id} { isFinished }
Kavita /api/Server/server-info-slim, POST /api/Series/on-deck, POST /api/Series/recently-added-v2, /api/Search/search?queryString=, covers /api/image/series-cover?seriesId=&apiKey= none
Shelfmark /api/status, /api/metadata/search?query=, /api/releases?provider=&book_id= POST /api/releases/download { book_data, release_data, context }

Every request has an 8 s timeout (searches through indexers 30 s, a Shelfmark release search 60 s, the health probes 3.5 s). Listings cache in memory for their palette's cadence (a queue 10 to 15 s, a calendar or wanted list 5 min, a TMDB title an hour); ⌘R refreshes; a request that fails while a cached answer stands keeps the cached rows (logged), except a refused key.

Design notes

  • Unconfigured means quiet, not broken. A service with no settings is one row in the Theater palette and nothing else at the root: with 33 palettes over 16 services, a hint row per palette would spam the root of a user who set up two services.
  • The arrs share one module (arr.ts): the same queue, calendar, wanted, history, lookup and add code with an App row for where the nouns differ (movie, series, artist; /api/v3 and /api/v1; the web routes). Sync watchlist runs ImportListSync per enabled list by definitionId, which sidesteps the Trakt list's hardcoded 12 h interval (the whole-app sync honours it).
  • SABnzbd and qBittorrent are one Downloads palette and one bar item: the question is "what is coming in", not which client has it. The history stays per client because failure reasons are.
  • The bar popovers are one render (view.ts): rows with a glyph or a poster, a tag, a figure and a progress bar, a cursor the arrows move, the keys per item (space, a, d, backspace, p). Every destructive key asks first.
  • Nothing is hardcoded: no host, key or password in the code; http://marko:8096 and the like appear only as placeholders in the settings.

Tests and the fixture

host/test/extensions/theater.test.ts runs the extension through the host harness against theater-mock.ts, one Bun server with every service under its own prefix; the writes are exercised there and never against a live stack. bun run extensions/theater/fixture.ts writes the gallery fixtures (app/src/gallery/shots/theater.json, bar-theater.json) from the same mock.