Theater
Jellyfin, the arr apps, downloads, music and books in one place
palupdated 2026-09-29ProductivityIn pal's registryMenu bar: Downloads, Now Playing, Requests, Arr queue7 deep links
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.






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;
enteropens its web page,cmd+enterits 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
-
Theater
theaterliverefreshes every 30sEvery 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
-
Jellyfin
jellyfinliverefreshes every 1 minContinue 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 Jellyfin
jellyfin-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 Jellyfin
jellyfin-playingliverefreshes every 30sActive 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 on
jellyfin-playlistThe picker Play on a device opens: every Jellyfin session that takes remote control; Enter plays the item there.
- ↵Play the item on that session
-
Requests
seerr-requestsliverefreshes every 1 minJellyseerr 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 title
seerr-requestinputfallback row: Request “…” on JellyseerrSearch 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
-
Radarr
radarrliverefreshes every 1 minThe 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 movie
radarr-addinputfallback row: Add “…” to RadarrLook 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 movies
radarr-wantedlistrefreshes every 5 minMonitored movies without a file, newest release first; Enter searches for one, cmd+Enter opens it.
- ↵Search for the movie now
- ⌘↵Open in Radarr
-
Radarr history
radarr-historylistrefreshes every 5 minWhat Radarr grabbed, imported or failed on lately.
- ↵Open the movie in Radarr
-
Sonarr
sonarrliverefreshes every 1 minThe 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 series
sonarr-addinputfallback row: Add “…” to SonarrLook 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 episodes
sonarr-wantedlistrefreshes every 5 minMonitored episodes that aired without a file, newest first; Enter searches for one.
- ↵Search for the episode now
- ⌘↵Open the series in Sonarr
-
Sonarr history
sonarr-historylistrefreshes every 5 minWhat Sonarr grabbed, imported or failed on lately.
- ↵Open the series in Sonarr
-
Lidarr
lidarrliverefreshes every 1 minThe 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 artist
lidarr-addinputfallback row: Add “…” to LidarrLook 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 albums
lidarr-wantedlistrefreshes every 5 minMonitored albums without files, newest first; Enter searches for one.
- ↵Search for the album now
- ⌘↵Open the artist in Lidarr
-
Lidarr history
lidarr-historylistrefreshes every 5 minWhat Lidarr grabbed, imported or failed on lately.
- ↵Open the artist in Lidarr
-
Downloads
downloadsliverefreshes every 20sSABnzbd 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 history
downloads-historylistrefreshes every 2 minWhat SABnzbd and qBittorrent finished lately, a failed one with its reason.
- ↵Open the download client
- ⌘⌫Remove from the history (asks first)
-
Indexers
prowlarrlistrefreshes every 5 minEvery 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 indexers
prowlarr-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 NZBHydra2
hydra-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
-
Navidrome
navidromeliverefreshes every 1 minWhat 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 Navidrome
navidrome-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
-
Audiobookshelf
absliverefreshes every 1 minContinue 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 Audiobookshelf
abs-searchinputfallback row: Search Audiobookshelf for “…”Books, podcasts and authors across every library.
- ↵Open in Audiobookshelf
- ⌘CCopy the link
-
Kavita
kavitaliverefreshes every 1 minContinue reading with the pages read, then the series added lately; Enter opens the web reader.
- ↵Open in Kavita
- ⌘CCopy the link
-
Search Kavita
kavita-searchinputfallback row: Search Kavita for “…”Series, collections and files across your libraries.
- ↵Open in Kavita
- ⌘CCopy the link
-
Request a book
shelfmarkinputfallback 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
-
Releases
shelfmark-releaseslistThe 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
-
Subtitles
bazarrlistrefreshes every 5 minBazarr: 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
Links 7
Theater answers these pal:// routes from a web page, a script or a keybind; pal call is the same route from a terminal. A link from a page asks first unless you trust the extension. How links work.
-
Open Search Jellyfin with a query typed
searchpal call theater/searchqWhat to search for
-
Open Request a title with a query typed
requestpal call theater/requestqThe title to look up on Jellyseerr
-
Open the Downloads palette
downloadspal call theater/downloads -
Pause SABnzbd and qBittorrent
pause-allpal call theater/pause-all -
Resume SABnzbd and qBittorrent
resume-allpal call theater/resume-all -
Run an import list sync now (Radarr, Sonarr)
sync-watchlistpal call theater/sync-watchlistappradarr or sonarr; both when omitted
-
Open a service's web UI
openpal call theater/open service=<service>service*jellyfin, seerr, radarr, sonarr, lidarr, prowlarr, hydra, bazarr, sab, qbit, navidrome, abs, kavita, shelfmark, filebrowser, homepage
Settings
Extension, [extensions.theater]
jellyfin_urljellyfin_keyDashboard › API Keys on the server.
jellyfin_userWhose Continue Watching and watched state; the first user when empty.
seerr_urlseerr_keySettings › General on Jellyseerr (Overseerr works the same).
radarr_urlradarr_keySettings › General › Security.
sonarr_urlsonarr_keylidarr_urllidarr_keyprowlarr_urlprowlarr_keyhydra_urlhydra_keyConfig › Main › API key.
bazarr_urlbazarr_keySettings › General › Security.
sab_urlsab_keyConfig › General › API key (the NZB key is not enough for the queue).
qbit_urlqbit_keyqBittorrent 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_userqbit_passwordnavidrome_urlnavidrome_usernavidrome_passwordSent as a Subsonic salted token, never in clear.
abs_urlabs_userabs_passwordkavita_urlkavita_userkavita_passwordshelfmark_urlThe book request door (no auth).
filebrowser_urlOpened from the Theater palette.
homepage_urlThe 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 anApprow for where the nouns differ (movie, series, artist;/api/v3and/api/v1; the web routes). Sync watchlist runsImportListSyncper enabled list bydefinitionId, 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:8096and 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.




