Fork: This project was forked from Bergasha/kino-swipe. It is maintained by @AndrewTheTechie.
Always trying to decide on a movie to watch together?, This may be the fun solution you've been looking for. Dating app style swipe right for like swipe left for nope, If you both swipe right on the same movie, IT'S A MATCH!!
- Jellyfin Integration: Connects directly to your server to pull random movies.
- Real-Time Sync: Host a room, share a 4-digit code, and swipe with a partner instantly.
- Visual Feedback: Faint Red/Green "glow" overlays that react as you drag the posters left or right.
- Select Genre: Both sessions will stay in sync while browsing genres.
- Add to watchlist: Tap on each match and either open in Jellyfin or add to watchlist for later.
- Watch trailer Tap on the main poster in swipedeck for full synopsis and even watch the trailer.
- PWA Support: Add it to your Home Screen for a native app feel.
- Match Notifications: Instant alerts when you both swipe right on the same movie.
- Match History All matches now live in Match History until you're ready to delete them.
- Solo Mode Flying solo? no worries, just host session and flick the solo toggle. (Every right swipe saves to Match History)
This application connects directly to a Jellyfin server to pull random movies from your library. Target Jellyfin 10.8+ unless you pin an older server—call out version quirks in ops notes if you diverge.
| Variable | Required when | Description |
|---|---|---|
SESSION_SECRET |
Always | session secret. |
TMDB_ACCESS_TOKEN |
Always | TMDB API key (trailers / cast). |
JELLYFIN_URL |
Always | Base URL of your Jellyfin server (no trailing slash). |
JELLYFIN_API_KEY |
Always | API key for unattended server access. |
JELLYFIN_DEVICE_ID |
Optional | Stable device id string sent with Jellyfin auth headers (default is built-in). |
CORS_ORIGINS |
Optional | Json encoded list of strings of cors origins. |
PUID |
Optional | UID the app runs as inside the container — set to match the owner of your bind-mounted ./data directory (default 99). |
PGID |
Optional | GID the app runs as inside the container — set to match the group of your bind-mounted ./data directory (default 100). |
This app stores Jellyfin user IDs in the database user_id field. Requests can include the user identity via:
X-Provider-User-Id(preferred neutral header), orX-Jellyfin-User-Id(Jellyfin-specific header),
and for user-scoped list actions must include a Jellyfin user token via:
Authorization: MediaBrowser ... Token=\"<user-token>\"(preferred), orX-Emby-Token(alternative header).
- Happy path: With valid
JELLYFIN_URLandJELLYFIN_API_KEY, start the app and hit provider endpoints (/genres,/movies,/jellyfin/server-info). Confirm logs show no API keys or access tokens. - API key rotation: Revoke the API key, restart, confirm failure, create a new API key, restart, and confirm authenticated
/Itemssucceeds again. - After recovery: Restart the process (or rely on the next provider use after
reset()) and hit/genresor create/join a room soget_provider()re-authenticates — you should be back to a working deck without pasting any tokens into logs or tickets.
JELLYFIN_URL=http://your-jellyfin-host:8096
JELLYFIN_API_KEY=your-jellyfin-api-key
TMDB_ACCESS_TOKEN=your-tmdb-read-access-token
SESSION_SECRET=long-random-string- Media backend: Jellyfin — see Media backend: Jellyfin and the env table above.
- TMDB Read Access Token — required at startup (trailers/cast); keep the token private.
- HTTPS/Reverse Proxy: To "Install" the app as a PWA on your phone so it looks like an app, you must access it over an HTTPS connection. If you access it over local ip, it will work in the browser but when added to homescreen it will just act as a shortcut not like an app.
Only required if you want trailers to work on the rear of the movie posters.
- Go to https://www.themoviedb.org/settings/api
- Log in or create a free TMDB account
- Under the API section, find Read Access Token (this is the v4 Bearer token)
- Copy the token value (not the API key)
- Set as
TMDB_ACCESS_TOKENenvironment variable
Pin to a versioned tag for stability. Substitute the latest release tag from the releases page:
image: ghcr.io/andrewthetechie/jelly-swipe:v0.1.0 # Pin to a release tagThe :latest tag tracks the rolling tip of main and may include unreleased changes — it is not the newest release. For production use, always pin to a versioned tag (vX.Y.Z).
If you are upgrading from a pre-PUID/PGID version and your ./data directory is owned by root, you have two options:
- Set
PUIDandPGIDto match the current owner:stat -c '%u %g' ./data - Or chown the directory once:
chown -R 99:100 ./data
The container exposes two unauthenticated endpoints:
GET /healthz— Liveness probe. Returns{"status": "ok", "version": "X.Y.Z"}with HTTP 200. Never touches Jellyfin or SQLite.GET /readyz— Readiness probe. Checks SQLite and Jellyfin in parallel. Returns{"status": "ok", "checks": {"sqlite": "ok", "jellyfin": "ok"}}with HTTP 200 when healthy, or{"status": "degraded", "checks": {"sqlite": "ok", "jellyfin": "fail: ..."}}with HTTP 503 if a dependency is unreachable.
services:
jelly-swipe:
image: ghcr.io/andrewthetechie/jelly-swipe:v0.1.0 # Pin to a release tag
container_name: jelly-swipe
ports:
- "5005:5005"
environment:
- JELLYFIN_URL=http://YOUR_JELLYFIN_IP:8096
- JELLYFIN_API_KEY=your-jellyfin-api-key
- SESSION_SECRET=SomeRandomString
- TMDB_ACCESS_TOKEN=your_copied_tmdb_token_here
- PUID=99 # UID for app process — match your ./data directory owner
- PGID=100 # GID for app process — match your ./data directory owner
volumes:
- ./data:/app/data
restart: unless-stoppeddocker run -d \
--name jelly-swipe \
-p 5005:5005 \
-e JELLYFIN_URL=http://YOUR_JELLYFIN_IP:8096 \
-e JELLYFIN_API_KEY=your-jellyfin-api-key \
-e SESSION_SECRET=SomeRandomString \
-e TMDB_ACCESS_TOKEN=your_copied_tmdb_token_here \
-e PUID=99 \
-e PGID=100 \
-v ./data:/app/data \
--restart unless-stopped \
ghcr.io/andrewthetechie/jelly-swipe:v0.1.0For Unraid users, a pre-configured template is provided at unraid_template/jelly-swipe.html. This template uses Jellyfin API key authentication and requires the following environment variables:
- JELLYFIN_URL — Base URL of your Jellyfin server (no trailing slash)
- JELLYFIN_API_KEY — API key for unattended server access
- TMDB_ACCESS_TOKEN — TMDB Read Access Token for trailers and cast information
- SESSION_SECRET — Random secret string for session security
All fields are blank by default and must be filled in by the user. The template does not expose username/password authentication options — it uses API key authentication only.
CI Validation: The Unraid template is automatically validated by a GitHub Actions workflow (.github/workflows/unraid-template-lint.yml) that ensures all template variables are a recognized subset of the application's environment variables. This prevents template drift and maintains consistency between the template and the application code.
For local development and contributing, use uv for dependency management. This project requires Python 3.13.
# Install dependencies from the committed lockfile
uv syncThis creates a virtual environment in .venv/ and installs all dependencies from pyproject.toml and uv.lock.
Development server (auto-reload):
uv run python -m jellyswipe.bootstrapProduction-style server (for testing):
uv run python -m jellyswipe.bootstrapAdd a new dependency:
uv add <package-name>Update the lockfile after dependency changes:
uv lock --upgradeCommit both pyproject.toml and uv.lock when adding or updating dependencies.
- The app is installed as a package (
jellyswipe/layout), not a script. Run it viauv run python -m jellyswipe.bootstrap. - All production deployment uses Docker (see Deployment section above). Local dev uses uv for fast iteration.
- Distribution is Docker-only (Docker Hub and GHCR). There is no PyPI package to install via pip.
"This product uses the TMDB API but is not endorsed or certified by TMDB."




