AI recommendations for Plex — what works and what doesn't

Updated By Steven Adams

Where a language model helps with Plex recommendations, where it invents films you don't own, what it costs nightly, and how to run it locally with no API key.

Short answer: an AI model — an LLM, short for large language model — is good at ranking and explaining a list of candidates. It is bad at producing that list. Ask a model “what should Sam watch?” and you get films you don’t own, films under alternate titles, and occasionally films that don’t exist. Generate candidates from your library first and the model becomes genuinely useful.

This page is about the shape of the problem, not about any one tool.

The failure everyone hits first

The obvious design is: dump the watch history into a prompt, ask for ten recommendations, create a collection from the answer. It demos beautifully and falls apart in a library.

The model doesn’t know what you own. It recommends from everything ever made. On a 2,000-film library most of what comes back isn’t there, so your row of ten becomes a row of three.

Titles don’t match cleanly. Films have alternate release titles, regional titles, remakes with the same name, and years that disagree between sources. “Which of my files is this?” is a real matching problem, and a model that answers confidently makes it worse.

It invents things. Not often, but often enough that an unattended nightly job will eventually publish a collection containing a film that has never existed to someone’s home screen.

It has no idea what they’ve watched unless you tell it, and telling it means putting the history in the prompt, which costs tokens and gets truncated on heavy viewers.

The shape that works

Invert it. Candidates from your library, ranking from the model.

  1. Take what the person actually watched and finished.
  2. Find similar titles through a metadata source — TMDB similarity, shared genres, keywords, cast, crew — or a recommendations service like Trakt.
  3. Filter to what’s in your library, and drop anything they’ve already seen.
  4. Then hand that shortlist to the model and ask it to pick the best ten and say why.

Now the model can’t invent anything, because it’s choosing from a list you control. Every pick provably exists. And you get the thing a model is actually good at: a sentence explaining why this film follows from that one, which is what makes a row feel considered rather than random.

Note that steps 1–3 need no AI at all. That’s worth knowing before you buy an API key — the structural work is code. Shortlist goes one step further: it does step 4 in code too, ranking the shortlist and writing the reason for each pick itself, so a model is never needed to explain a pick.

Do you need an API key?

No, and it’s worth understanding what you’re buying if you use one.

Without a model: candidate generation, filtering and scoring all run in code. You get a correct, personalised row, with a plain-English reason on every pick. What you don’t get is web search for recent titles TMDB misses, or an AI row you describe in your own words.

With a model: it can search the web for recent titles, and it can build an AI row from a description. Ranking and the reason for each pick are still done in code.

That’s the honest delta. It’s a nice improvement, not the difference between working and not working. Be suspicious of any tool that treats an API key as mandatory — it usually means the model is doing the candidate generation, which is the design that hallucinates.

Running it locally

A local model removes the cost and privacy questions entirely, and the ranking task is easy enough that small models do it well. The usual options are Ollama, llama.cpp, LM Studio, vLLM and LocalAI. All of them answer requests the same way OpenAI’s service does, so a tool built for OpenAI can usually be pointed at one by changing nothing but the web address.

Ranking twenty candidates and writing ten short sentences is not a demanding job. You do not need a 70-billion-parameter model for this, and a modest local one running on the same box as your server is a perfectly reasonable setup.

The same setting covers hosted gateways that speak this API — ollama.com’s cloud, OpenRouter — since the only difference is that they want an API key. Give the “Local / OpenAI-compatible” option the gateway’s address and its key; a server on your own network needs the address alone.

What it costs on a cloud provider

The bill is driven by how many people, how often, and how much you put in each prompt — not by which model brand.

Rough shape for a nightly run on a server with a couple of dozen users: you’re sending a shortlist of candidates plus a compact viewing summary per person, and getting back a small ranked list. That is a small number of tokens per user per night.

Two things blow it up:

Both are solvable in how the job is scheduled, which is why refresh cadence matters more to your bill than model choice.

Judging an AI-powered tool

Questions worth asking, whichever you pick:

Shortlist runs the inverted shape: candidates come from TMDB and Trakt, are filtered to titles in your library, then ranked and explained in code. An optional model (Claude, GPT, Gemini, or an OpenAI-compatible server you run yourself) only helps find titles, through web search, or builds an AI row.

Do it automatically

Shortlist does this for every person on your server, every night: it reads each person's own watch history, picks only titles already in your library, says why, and hides each row from everyone else with Plex sharing filters (the server owner and some restriction profiles are the exceptions). It is one self-hosted Docker container, and AI is optional.

A finished run in Shortlist: the result, duration, people and privacy summary, then the picks built for sarah, each with the reason it was chosen, such as Because you watched drama like GoodFellas.
A nightly run: every row says which watch it follows, so a bad pick can be traced back to its seed.
docker run -d --name shortlist -p 5959:5959 \
  -e TZ=Etc/UTC -e PUID=1000 -e PGID=1000 \
  -v /path/to/shortlist/config:/config \
  --restart unless-stopped \
  ghcr.io/stevezau/shortlist:latest

Getting started How it compares with other tools

Edit this page on GitHub