Build curated TV show playlists in Plex, matched by episode title.
- Title matching: works whether your rips use DVD or aired order. Two-parters match as separate parts ("Descent (1)", "Part II") or one combined file.
- In-place updates: rebuilding adds, removes and reorders items without recreating the playlist, so custom posters survive.
- Posters: generate title cards over artwork from your Plex library, or import your own image and have it cropped to fit.
- Shows as TOML: ships with The X-Files, five Star Trek series (the original, The Next Generation, Deep Space Nine, Voyager, Enterprise), Babylon 5, Defiance, Stargate SG-1 and Stargate Atlantis; add your own without writing Python.
- Interactive app: run
plexlistswith no command for a Textual TUI to browse, check, build and manage posters. Every action is also a CLI command for scripting. - Secure login: Plex's JWT device flow, with the key in your OS keychain and HTTPS-only connections.
uv tool install ~/Development/plexlists # or: uv tool install git+https://…
plexlists --helpFrom the project folder without installing: uv run plexlists …
plexlistsShows are listed on the left, collapsed; open one to see its playlists (z
opens or closes them all). Select a show to act on all of its playlists, or
select a single playlist.
With a saved login the app connects to Plex in the background as it opens and checks every playlist, so the match marks and episode details fill in by themselves; shows your server doesn't have are dimmed. The header shows how far that check has got, and the connection state: 🟢 connected, 🟡 connecting, 🔴 the last attempt failed, ⚪ not connected (or not logged in).
Each playlist shows when it was created and last updated in Plex. Plex keeps those times itself, per server; plexlists reads them whenever it connects and remembers the last answer, so they're shown as soon as the app opens.
Opening a playlist lists each entry's episode number, air date, length and
whether you've watched it, with Plex's summary of the highlighted episode
underneath, and marks the next one to watch. The tree and the show view give
each playlist's progress ("12/27"). This loads by itself once connected; without a saved login, press
c to connect.
| Key | Action |
|---|---|
c |
Check: match episodes against your library without changing Plex |
b |
Build: create or update playlists, and upload changed posters |
p |
Generate title-card posters from Plex artwork (asks before replacing existing ones) |
v |
View the selected playlist's poster in your image viewer |
o |
Open the posters folder in Finder |
e |
Edit the show file in $EDITOR (copies a built-in show to your folder first) |
x |
Delete playlists from Plex (asks first) |
r |
Reload show files and re-read your Plex library |
z |
Collapse all shows in the list, or expand them again |
/ |
Filter the list by show, playlist or episode title (esc clears it) |
a |
Account: log in or out |
g |
Hide or show the log |
q |
Quit |
plexlists login # once: approve this device on plex.tv
plexlists shows # available shows
plexlists tng list # playlists for a show
plexlists tng show borg # episodes in a playlist
plexlists tng show borg --plex # …with episode numbers, air dates, watched
plexlists tng list --plex # …with matches, created dates, progress
plexlists tng build --all --dry-run # check matches and what would change
plexlists tng posters --all # optional: generate title-card posters
plexlists tng poster borg ~/borg.png # or use your own image (file or URL)
plexlists tng build --all # create / update playlists in Plex
plexlists tng build --all --user kid # for a managed or shared user, not you
plexlists tng build --all --collection # as collections, which every user sees
plexlists tng remove borg # delete a playlist from Plex
plexlists status # login state and token expiryTab completion: plexlists --install-completion.
plexlists build-all builds every playlist of every show your server has, and
skips the shows it doesn't. Run it from cron or launchd so playlists fill in as
you add seasons:
# every night at 03:30; --quiet prints only what changed or went wrong
30 3 * * * /Users/you/.local/bin/plexlists build-all --quietIt exits with status 1 if a playlist couldn't be built. The saved login renews its token when the command runs.
Put images in <config dir>/posters/<show>/, named by playlist key:
| File | Used as |
|---|---|
borg.jpg |
Poster. Square, around 1000×1000. |
borg-art.jpg |
Background art. 16:9. |
png and webp also work. build uploads an image when it's new or changed.
plexlists paths shows where <config dir> is (on macOS,
~/Library/Application Support/plexlists).
You don't have to place the files by hand:
plexlists tng posters --all # title cards over artwork from Plex
plexlists tng posters --all --plain # text on a gradient, no Plex needed
plexlists tng poster borg ~/Downloads/x.png # your own image, cropped to a square
plexlists tng poster borg https://…/x.jpg --art # …or to 16:9 background art
plexlists tng pull-posters --all # save artwork you chose in Plex itselfposters also saves the same artwork, without the text, as the playlist's
background art if it doesn't have any (--no-art turns that off).
posters skips playlists that already have an image, and poster refuses to
replace one, unless you pass --force.
A generated poster uses the still of the playlist's first episode. To choose,
set poster_from on the playlist in the show file:
[[playlists]]
key = "borg"
poster_from = "Q Who" # an episode title: its still
# poster_from = "first_contact" # a key from [films]: the film's art
# poster_from = "show" # the show's backgroundIf Plex can't be reached or has no artwork, the poster falls back to the plain gradient.
plexlists new farscape --title "Farscape" # from a template
plexlists new tng --title x --copy tng # customize a built-in showThis writes <config dir>/shows/<name>.toml. A file there with the same name as a
built-in show replaces it. The format is documented at the top of
src/plexlists/models.py, and src/plexlists/shows/ has full examples.
| Variable | Purpose |
|---|---|
PLEX_URL, PLEX_TOKEN |
Server URL / legacy token instead of login (not recommended) |
PLEXLISTS_<SHOW>_TITLE |
Show title in your Plex library, e.g. PLEXLISTS_TNG_TITLE |
PLEXLISTS_CONFIG_DIR |
Use a different config directory |
uv sync # create .venv with dev tools
uv run pytest # tests (offline, against a fake Plex server; includes TUI tests)
uv run textual run --dev plexlists.tui:PlexlistsApp # TUI with live CSS reload
uv run ruff check . # lint
uv run ruff format . # format
uv run ty check # type check| Module | Contents |
|---|---|
models.py |
Show file format and loader |
titles.py, matching.py |
Title normalization and episode matching |
sync.py |
Finding shows and films in Plex, in-place playlist sync, artwork upload |
posters.py |
Generated posters |
auth.py, config.py |
Plex login, keychain storage, config paths |
service.py |
Plex operations shared by the CLI and TUI |
report.py |
Result formatting |
cli.py |
Typer CLI |
tui.py |
Textual app |