decide

decide

Docker app from hk21x's Repository

Overview

decide — Swipe. Decide. Watch. A self-hosted swipe-to-match film picker for your Plex library. Two to four people swipe through the same small, filtered deck of films from your server; when everyone says "Tonight" to the same one it's a match, a Final Round crowns the winner, and ticket stubs remember your movie nights. Features: film AND whole-series decks, mood-based filters (runtime, rating, certificate, collections), join codes with a dual QR (local + Tailscale/remote), live matches over WebSocket plus push alerts, async swiping (together or hours apart), a Final Round that crowns tonight's pick, taste-compatibility stats, a keepsake stub album, "Open in Plex" and play-on-your-TV, installable PWA with offline swiping, dark and light themes. Privacy: your Plex token never leaves the container — every Plex call, including artwork, is proxied by the backend. Once the library is synced, everything works on the LAN with no internet access. First run: open the WebUI and follow the setup wizard — sign in with Plex via a 4-character code at plex.tv/link (decide never asks for your password), pick your server and film libraries, and let the first sync run. decide is an independent project that works with Plex Media Server. It is not affiliated with or endorsed by Plex, Inc.

Contributors Forks Stargazers Issues MIT License


Logo

decide

Swipe. Decide. Watch. - a self-hosted swipe-to-match film picker for your Plex library.

Report Bug · Request Feature

Table of Contents
  1. About The Project
  2. Getting Started
  3. Usage
  4. Configuration
  5. Deployment Notes
  6. Roadmap
  7. Development
  8. Contributing
  9. License
  10. Contact
  11. Acknowledgments
  12. Disclaimer

About The Project

Swiping a film card Starting a session - moods, filters and deck size Sharing the join code and QR Choosing between films The Final Round crowning tonight's film
Swipe · start a session · share the code · pick · the Final Round crowns one

Movie night has a deadline, and "what shall we watch?" shouldn't eat half of it. decide turns the choice into a game: two to four people swipe through the same small deck of films from your Plex library; when everyone says "Tonight" to the same one, it's a match - and a ticket stub prints.

  • Small decks, not endless scrolling. Films or whole series - filter by mood, runtime, rating, certificate, or deal from a Plex collection; swipe 20–50 cards, not 3,000.
  • Together or apart. Everyone swipes the same frozen deck in the same order, live on the sofa or hours apart on the train. Matches land the moment you agree - with push alerts when you're not looking, and recent sessions waiting on the Home screen when you come back.
  • It actually decides. Too many matches? The Final Round runs them head-to-head until one film is crowned - then open it in Plex, or send it straight to the TV.
  • Nights worth keeping. Every session ends with a taste-compatibility score ("You and Dee agreed on 7 of 30 - 23% tonight"), and stubs you keep live on in the album long after sessions expire.
  • Private by design. Your Plex token never leaves the server - every Plex call, including artwork, is proxied by the backend. Once synced, it works on the LAN with no internet at all.
  • Feels like an app. Installable PWA with offline swiping that syncs back exactly once when the connection returns. Dark and light themes.

decide is an independent project that works with Plex Media Server. It is not affiliated with or endorsed by Plex, Inc.

(back to top)

Built With

  • React
  • TypeScript
  • Vite
  • Tailwind CSS
  • Framer Motion
  • FastAPI
  • Python
  • SQLite
  • Docker

(back to top)

Getting Started

decide runs as a single Docker container alongside your Plex Media Server - one image, one port, one SQLite file.

Prerequisites

  • Docker (or any OCI runtime) on a machine that can reach your Plex server - a Pi, NAS or home server on the same LAN is ideal
  • A Plex Media Server with at least one movie library

Installation

  1. Create a compose.yaml:

    services:
      decide:
        image: ghcr.io/hk21x/decide:latest
        container_name: decide
        ports:
          - "8080:8080"
        volumes:
          - ./data:/data
        environment:
          - PUID=1000
          - PGID=1000
        restart: unless-stopped
    
  2. Start it:

    docker compose up -d
    
  3. Open http://<host>:8080 and follow the setup wizard: sign in with Plex (a 4-character code at plex.tv/link - decide never asks for your password), pick your server and film libraries, and let the first sync run.

Unraid: a Community Applications template ships in hk21x/unraid-templates - once accepted you'll find decide in the Apps tab; until then, the template XML works with Unraid's "Add Container → Template" as well.

(back to top)

Usage

  1. Start a session - pick a mood (or fine-tune genres), a runtime cap, a minimum rating, a certificate ceiling for family nights, or a Plex collection to deal from. A live counter shows how many films match.
  2. Share the code - a six-character join code, a copyable link, and a dual QR: one for people on your Wi-Fi, one for remote friends on your Tailscale network (Settings → Access & sharing). Lost your place? Sessions reappear on Home, and rejoining never burns one of the four seats.
  3. Swipe - right means "I'd watch this tonight". Tap ⓘ for the synopsis, cast and backdrop. Undo within five seconds if your thumb betrayed you.
  4. Match - unanimous right-swipes print a ticket stub in the shared shortlist, along with how compatible your taste turned out to be tonight.
  5. Crown one - the Final Round puts the matches head-to-head; pass the phone, argue, tap winners until one film is crowned for everyone. Open it in Plex, or play it on any Plex client the server can see.
  6. Keep the stub - kept stubs (and crowned winners) go to the 🎟 album, a ticket wallet of past movie nights that outlives the sessions.

On iPhone or Android, open the site and Add to Home Screen - decide installs as an app, and swipes made offline sync back when you reconnect. There's a light theme in Settings if the auditorium look isn't yours.

(back to top)

Configuration

Everything is optional - the wizard covers the lot. Env vars exist for declarative setups:

Variable Default Purpose
PORT 8080 Listen port
DATA_DIR /data SQLite database + artwork cache
PUID / PGID 1000 Run as this user/group
ART_CACHE_MB 500 Artwork disk-cache cap
PLEX_URL - Skip the wizard: server address
PLEX_TOKEN - Skip the wizard: auth token
PLEX_SECTIONS - Comma-separated movie section ids
SECRET_KEY auto-generated Cookie-signing key override
LOCAL_URL auto-detected Join-QR address for the local network
REMOTE_URL - Join-QR address for remote access (e.g. Tailscale)

(back to top)

Deployment Notes

decide uses a WebSocket at /api/sessions/*/live - make sure upgrades pass through your reverse proxy.

Caddy (WebSockets work out of the box):

decide.example.com {
    reverse_proxy 127.0.0.1:8080
}

nginx:

location / {
    proxy_pass http://127.0.0.1:8080;
    proxy_http_version 1.1;
    proxy_set_header Upgrade $http_upgrade;
    proxy_set_header Connection "upgrade";
    proxy_set_header Host $host;
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
}

Remote access is deliberately out of scope - bring whatever already works for the rest of your self-hosted stack. Tailscale is the easiest route if you have nothing yet.

If your Plex server runs in Docker and LAN clients appear as remote (source-IP NAT), that's a Plex networking quirk, not a decide one - see the Plex forum thread on Docker and local network discovery.

(back to top)

Roadmap

  • Final Round - head-to-head bracket that crowns tonight's film
  • Taste-compatibility stats
  • The stub album - kept tickets that outlive sessions
  • Decks from Plex collections
  • Play the crowned film on a Plex client (TV, etc.)
  • Series-level TV decks (swipe whole shows - never episodes)
  • Recent sessions + rejoin, match push alerts, Tailscale dual-QR
  • Jellyfin support (media access already sits behind a MediaSource protocol - a JellyfinSource drops in without touching the rest)
  • Per-person Plex sign-in, so "unwatched" means your unwatched
  • Save the shortlist to a Plex playlist

See the open issues for the full list of proposed features and known issues.

Deliberately not planned: video playback (that's Plex's job), TV shows, more than four people, recommendations, or accounts.

(back to top)

Development

No build step magic - a Python backend and a Vite frontend.

Backend (Python 3.12, FastAPI, SQLite - managed with uv):

cd backend
uv sync
DATA_DIR=.dev-data uv run uvicorn decide.main:app --reload --port 8080
uv run pytest

Frontend (React 18 + Vite + TypeScript + Tailwind):

cd frontend
npm install
npm run dev   # proxies /api to :8080

Verified Plex endpoint behaviour is documented in docs/plex-notes.md.

(back to top)

Contributing

Contributions are what make the open source community such an amazing place to learn, inspire, and create. Any contributions you make are greatly appreciated.

If you have a suggestion that would make this better, please fork the repo and create a pull request. You can also simply open an issue with the tag "enhancement".

  1. Fork the Project
  2. Create your Feature Branch (git checkout -b feature/AmazingFeature)
  3. Commit your Changes (git commit -m 'Add some AmazingFeature')
  4. Push to the Branch (git push origin feature/AmazingFeature)
  5. Open a Pull Request

(back to top)

License

Distributed under the MIT License. See LICENSE for more information.

(back to top)

Contact

Harry - @hk21x

Project Link: https://github.com/hk21x/decide

(back to top)

Acknowledgments

(back to top)

Disclaimer

decide was built with support from AI coding assistants. Every change has been reviewed by a human and will continue to be, tested thoroughly before each merge. This project was built for my partner and me, before realising some of my friends using Plex might benefit from it too. A human (just me, for now) is always responsible for pulling the trigger on any committed change, and takes responsibility for any actions.

It's still a one-person hobby project rather than an audited product. By design, there is no user authentication - anyone who can reach the port can start a session and browse your library metadata. Run it the way you'd run any other self-hosted service: on your LAN or behind a tailnet, not exposed to the internet.

(back to top)

Install Decide on Unraid in a few clicks.

Find Decide in Community Apps on your Unraid server, review the template, and click Install. Unraid handles the Docker app or plugin setup from the published template.

Open the Apps tab on your Unraid server Search Community Apps for Decide Review the template variables and paths Click Install

Related apps

Details

Repository
ghcr.io/hk21x/decide:latest
Last Updated2026-09-08
First Seen2026-07-24

Runtime arguments

Web UI
http://[IP]:[PORT:8080]
Network
bridge
Shell
sh
Privileged
false

Template configuration

WebUI PortPorttcp

Port for the decide web app.

Target
8080
Default
8080
Value
8080
DataPathrw

SQLite database and artwork cache.

Target
/data
Default
/mnt/user/appdata/decide
Value
/mnt/user/appdata/decide
PUIDVariable

User ID to run as (99 = nobody on Unraid).

Default
99
Value
99
PGIDVariable

Group ID to run as (100 = users on Unraid).

Default
100
Value
100
Artwork cache cap (MB)Variable

Disk cap for the poster/backdrop cache under /data/art.

Target
ART_CACHE_MB
Default
500
Value
500
Plex URL (optional)Variable

Optional declarative setup: your Plex server address (e.g. http://192.168.1.10:32400). Leave empty to use the in-browser setup wizard instead.

Target
PLEX_URL
Plex token (optional)Variable

Optional declarative setup: an X-Plex-Token. Leave empty to sign in with the wizard's plex.tv/link flow instead.

Target
PLEX_TOKEN
Local join address (optional)Variable

Address the join QR shows to people on your LAN (e.g. http://192.168.1.10:8080). Auto-detected if empty; also settable in-app.

Target
LOCAL_URL
Remote join address (optional)Variable

Address the join QR shows to remote friends (e.g. your Tailscale https://... name). Also settable in-app.

Target
REMOTE_URL
Plex sections (optional)Variable

Optional declarative setup: comma-separated movie library section ids (e.g. 1,4).

Target
PLEX_SECTIONS