Swiparr

Swiparr

Docker app from grtgbln's Repository

Overview

Swiparr is a fun way to discover and decide what to watch next from your Jellyfin media library. Think "Tinder for Movies" – swipe through your library, match with friends in a session, and find something everyone wants to watch.

Swiparr

Swiparr 🍿

Discover what to watch next, by yourself or together.

Swiparr turns the dreaded "what should we watch?" question into a fun, collaborative experience.
Like Tinder for movies, but smarter and works for groups.

License Docker Buy Me a Coffee


🎯 The Problem

The struggle is real: 30 minutes of "what should we watch?" that ends with watching the same show again. Swiparr fixes this by:

✨ Turning discovery into a fun, game-like experience
🤝 Finding content everyone actually wants to watch
⚡ Making group decisions in minutes, not hours
🌍 Working with your existing media libraries OR standalone


✨ Features

🎬 Content Discovery

  • Intuitive Swipe Interface - Browse movies with a familiar card-based design
  • Multi-Provider Support - Works with Jellyfin, Emby, Plex, or TMDB directly
  • Smart Matching - Automatically finds content everyone in your group will enjoy
  • Mobile-First - Optimized for phones, with desktop keyboard shortcuts
  • PWA Ready - Install as a web app for the best experience

👥 Built for Groups

  • Instant Sessions - Create or join in seconds, no complex setup
  • Flexible Match Rules - Choose "any two people" or "everyone must agree"
  • Session Controls - Limit likes, dislikes, or total matches
  • Watchlist Sync - Seamlessly save favorites back to your media server

🔧 Universal Compatibility

  • Jellyfin - Full native integration
  • Emby - Experimental support (improving)
  • Plex - Experimental support (improving)
  • TMDB - No media server required, works standalone

🚀 Quick Start

Fastest: Swiparr Global

No setup, no server, no problem.

🌐 swiparr.com - Free to use, community-supported (hopefully)

Easiest: Deploy to Vercel

One-click deployment, perfect for personal or small group use:

Deploy with Vercel

Note: The automatic deployment workflow in Vercel uses the Turso integration by default as a database service provider. Free to set up, possible to swap out [^1].

Vercel security note: AUTH_SECRET is auto-generated during the build (via scripts/ensure-auth-secret.cjs) and persisted in the database when not provided.

Full Control: Self-Host with Docker

Using Docker Compose (Recommended):

  1. Create docker-compose.yml:
services:
  swiparr:
    image: ghcr.io/m3sserstudi0s/swiparr:latest
    container_name: swiparr
    restart: unless-stopped
    environment:
      - PROVIDER=jellyfin  # or plex, emby, tmdb (or set PROVIDER_LOCK to "false")
      - JELLYFIN_URL=http://your-jellyfin:8096 # adjust to provider, none without server lock
    volumes:
      - ./swiparr-data:/app/data
    ports:
      - 4321:4321
  1. Run it:
docker compose up -d

Using Docker CLI:

docker run -d \
  --name swiparr \
  --restart unless-stopped \
  -p 4321:4321 \
  -v $(pwd)/swiparr-data:/app/data \
  -e PROVIDER=jellyfin \
  -e JELLYFIN_URL=http://your-jellyfin:8096 \
  ghcr.io/m3sserstudi0s/swiparr:latest
  1. Open http://localhost:4321

Docker security note: AUTH_SECRET is auto-generated on first boot (via scripts/ensure-auth-secret.cjs) and stored in the database when not provided.


⚙️ Configuration Reference

Provider-Specific Settings

Choose one provider setup based on your needs:

Jellyfin Setup
PROVIDER=jellyfin
JELLYFIN_URL=http://your-jellyfin:8096              # Internal URL (required)
JELLYFIN_PUBLIC_URL=https://jellyfin.example.com    # Public URL (optional)
JELLYFIN_USE_WATCHLIST=false                         # Use Watchlist (plugin needed) vs Favorites (optional)
Emby Setup (Experimental)
PROVIDER=emby
EMBY_URL=http://your-emby:8096        # Internal URL (required)
EMBY_PUBLIC_URL=https://emby.example.com  # Public URL (optional)
Plex Setup (Experimental)
PROVIDER=plex
PLEX_URL=http://your-plex:32400       # Internal URL (required)
PLEX_PUBLIC_URL=https://plex.example.com # Public URL (optional)
TMDB Setup (No Server Required)
PROVIDER=tmdb
TMDB_ACCESS_TOKEN=your-tmdb-token     # API Read-Only Token (required)
TMDB_DEFAULT_REGION=SE                # Default region for availability/certifications (optional)

Security & Advanced Options

# Authentication
AUTH_SECRET=random-string-32-chars-min     # Auto-generated on boot/build when not provided. See Security & Privacy.
USE_SECURE_COOKIES=true                    # Required for HTTPS

# Application
PORT=4321                                  # Default port
HOSTNAME=0.0.0.0                          # Bind address
DATABASE_URL=file:/app/data/swiparr.db    # SQLite path or Turso URL
DATABASE_AUTH_TOKEN=your-token            # Required for Turso/Remote DB

# Base path (build-time only — see Custom Base Path section)
# URL_BASE_PATH=/swipe

# Admin
ADMIN_USERNAME=your-username                      # Global auto-grant admin privileges
JELLYFIN_ADMIN_USERNAME=jelly-admin               # Provider-specific admin (overrides global)
PLEX_ADMIN_USERNAME=plex-admin                   # Provider-specific admin (overrides global)
EMBY_ADMIN_USERNAME=emby-admin                   # Provider-specific admin (overrides global)

# Security Headers
X_FRAME_OPTIONS=DENY                       # Frame control
CSP_FRAME_ANCESTORS=none                   # Embedding policy

# Network Safety
ALLOW_PRIVATE_PROVIDER_URLS=false          # Block private/LAN URLs for user-supplied providers (BYOP)
PLEX_IMAGE_ALLOWED_HOSTS=plex.example.com,*.plex.direct  # Optional extra image hosts

# BYOP Mode - Bring Your Own Provider
PROVIDER_LOCK=false                          # Let users choose and configure their own provider

# Misc
USE_ANALYTICS=false                          # Enable anonymous usage analytics (Vercel deployments)
ENABLE_DEBUG=false                           # Enable verbose debug logging and client-server error mapping
USE_STATIC_FILTERS=false                     # Skip dynamic filter fetching; use built-in genre/year/rating lists instead (useful for very large libraries where filter API calls time out)

Environment Variable Matrix

Variable Required? Default Description
PROVIDER ✳️ jellyfin Primary media provider (jellyfin, tmdb, plex, emby)
PROVIDER_LOCK true If true, users cannot change the provider at runtime
JELLYFIN_URL ✳️ - Internal URL of your Jellyfin server
JELLYFIN_PUBLIC_URL - Public URL of your Jellyfin server (for client-side access)
JELLYFIN_USE_WATCHLIST false Use Jellyfin Watchlist instead of Favorites
EMBY_URL ✳️ - Internal URL of your Emby server
EMBY_PUBLIC_URL - Public URL of your Emby server (for client-side access)
PLEX_URL ✳️ - Internal URL of your Plex server
PLEX_PUBLIC_URL - Public URL of your Plex server (for client-side access)
PLEX_TOKEN - Plex Admin/Access Token
TMDB_ACCESS_TOKEN ✳️ - TMDB API Read-Only Access Token
TMDB_DEFAULT_REGION SE Default TMDB region (ISO 3166-1) for streaming availability/certifications
AUTH_SECRET Auto-generated on boot/build Secret used for session encryption and guest lending token encryption (min 32 chars). See Security & Privacy.
USE_SECURE_COOKIES false Set to true for HTTPS deployments
DATABASE_URL file:/app/data/swiparr.db SQLite path or Turso URL [^1]
DATABASE_AUTH_TOKEN - Auth token for remote databases (e.g. Turso)
APP_PUBLIC_URL swiparr.com The public domain where the app is hosted
URL_BASE_PATH - Base path for subpath deployments (e.g. /swipe). Must be set at image build time — see Custom Base Path.
ADMIN_USERNAME - Global admin username (overrides provider-specific) [^2]
JELLYFIN_ADMIN_USERNAME - Jellyfin-specific admin username [^2]
EMBY_ADMIN_USERNAME - Emby-specific admin username [^2]
PLEX_ADMIN_USERNAME - Plex-specific admin username [^2]
X_FRAME_OPTIONS DENY Security header: X-Frame-Options
CSP_FRAME_ANCESTORS none Security header: Content-Security-Policy frame-ancestors
ALLOW_PRIVATE_PROVIDER_URLS false Allow private/LAN provider URLs for BYOP user inputs
PLEX_IMAGE_ALLOWED_HOSTS - Extra allowlist for Plex image hosts (comma-separated). PLEX_URL/PLEX_PUBLIC_URL are allowed by default.
USE_ANALYTICS false Enable anonymous usage analytics (Vercel deployments)
ENABLE_DEBUG false Enable verbose debug logging and client-server error mapping
USE_STATIC_FILTERS false Skip dynamic filter fetching and use built-in genre/year/rating lists. Useful for very large libraries where filter API calls time out.

[^1]: Can be set to a local file (internal to container) OR external URL. Mostly relevant for Vercel deployments, which uses the Turso integration in the set-up workflow by default where these values are auto-generated and -injected. Can of course be swapped out with a database service provider of choice.

[^2]: Only applicable for providers with authentication (Jellyfin, Plex, Emby). Admin role ownership is tracked per-provider. Defaults to the first user of that provider that logs in (if supported), or matching env vars. Admin capabilities are disabled for providers without built-in authentication (like TMDB).

✳️ = Required conditionally


Features (Deep dive)

Session Settings

When you create a session, customize it for your group:

Match Strategies
  • Two or More: Any two people liking the same content creates a match

    • Best for: Larger groups where majority rules
    • Finding: Quick results, more options
  • Unanimous: Everyone must like it for a match

    • Best for: Smaller groups wanting guaranteed crowd-pleasers
    • Finding: Fewer but higher-quality matches
Session Restrictions
  • Max Likes: Limit right swipes per person

    • Forces thoughtful, selective choices
    • Prevents mindless approval
  • Max Nopes: Limit left swipes per person

    • Stops serial negativity
    • Encourages open-mindedness
  • Max Matches: Auto-stop when you have enough options

    • Perfect for when you just need 3-4 solid picks
Guest Lending (Account Sharing)

How it works:

  1. Host enables "Guest Lending" in settings
  2. Guest joins session with just a name - no account needed
  3. Swiparr uses the host's credentials to fetch content
  4. Guest gets a unique ID, their swipes are tracked separately
  5. Guests cannot access host account or modify settings

Security note: Host credentials are stored server-side and encrypted at rest using AUTH_SECRET while Guest Lending is enabled. See "Generating AUTH_SECRET" in Security & Privacy.

Perfect for: Movie nights with friends who don't have media servers

Admin Role

Automatically Assigned: First user to log in for each provider becomes that provider's admin.

Manual Assignment: Set ADMIN_USERNAME (global) or [PROVIDER]_ADMIN_USERNAME (e.g., JELLYFIN_ADMIN_USERNAME) environment variables.

Admin Privileges:

  • Configure included media libraries for the provider
  • Manage global provider settings
  • Override session restrictions
  • Access admin dashboard (only for providers with authentication)

🔄 Provider Flexibility: Two Modes

Server Lock Mode

PROVIDER_LOCK=true

One provider, admin-controlled

  • Admin configures ONE provider in environment variables
  • All users automatically use this provider
  • Best for: Families, roommates, shared media servers

BYOP Mode

PROVIDER_LOCK=false

Bring Your Own Provider

  • Each user connects their own provider during onboarding
  • Users can switch providers anytime
  • Best for: Users with different media servers, and/or you have none

🔒 Security & Privacy

  • Generating AUTH_SECRET (optional):
# macOS and Linux
openssl rand -base64 32

Windows users can use https://generate-secret.vercel.app/32.

  • Encrypted Sessions: iron-session with secure, encrypted cookies
  • Encrypted Guest Lending Tokens: host access tokens are encrypted at rest when Guest Lending is enabled
  • Scoped Access: Guests can only swipe, no account access
  • Data Ownership: Self-hosted = your data stays on your server
  • Provider Isolation: No credential sharing in BYOP mode
  • CORS Protection: Configured for safe media server integration
  • Security Headers: X-Content-Type-Options, X-XSS-Protection, CSP, Referrer-Policy
  • Network Safety: Private/LAN provider URLs are blocked by default; enable via ALLOW_PRIVATE_PROVIDER_URLS
  • Mode Awareness: Env-configured providers are trusted when PROVIDER_LOCK=true; user-supplied URLs are checked

🤝 Contributing

Swiparr is now open for contributions! 🎉

How to Contribute

  1. Start with Discussion - Propose changes before coding
  2. Fork & Develop - After discussion approval
  3. Pull Request - With clear description and tests

Development Setup

git clone https://github.com/m3sserstudi0s/swiparr.git
cd swiparr
npm install
npm run dev          # Start dev server
npm run lint         # Check code style

Contribution Areas

  • Provider Integrations: Improve Emby/Plex support
  • UI/UX: Mobile responsiveness, accessibility
  • Performance: Optimize queries, bundle size
  • Documentation: Examples, guides, tutorials
  • Testing: Add test coverage (currently minimal)

First-time contributors welcome! Start with "good first issue" discussions.


💚 Support the Project

Swiparr is free, open source, and community-supported. Your contributions help:

  • Buy Me a Coffee - Quick one-time support
  • 🌟 Star on GitHub - Show your support (it's free!)
  • 🏢 Use swiparr.com - The hosted version includes infrastructure funding

All support directly funds development and infrastructure costs.


📞 Community & Support

All support, questions, and discussions happen in GitHub Discussions:

Topic Link
❓ Questions & Help Ask a Question
💡 Feature Ideas Propose a Feature
🐛 Bug Reports Report a Bug
🙌 General Chat Start a Discussion

🐳 Docker Advanced Topics

Custom Base Path

If you want to serve Swiparr under a subpath — e.g. https://jellyfin.example.com/swipe/ — you need to set URL_BASE_PATH at image build time, not as a runtime environment variable.

Why? Next.js bakes asset URLs (/_next/static/...) into the compiled output at build time. Setting a base path only at runtime can fix page routing but leaves all JS/CSS/image references pointing at the wrong path, breaking the app. The prefix must be known before the build.

The prebuilt image from ghcr.io does not support URL_BASE_PATH — it is built without a base path. You must build your own image.

If you are not using compose and want to build the image

git clone https://github.com/m3sserstudi0s/swiparr.git
cd swiparr
docker build --build-arg URL_BASE_PATH=/swipe -t swiparr-custom .

Then use swiparr-custom as your image name in your docker run command.

If you are using compose, compose can build and manage the image for you at runtime

services:
  swiparr:
    pull_policy: build
    build:
      context: https://github.com/m3sserstudi0s/swiparr.git
      args:
        URL_BASE_PATH: /swipe
    container_name: swiparr
    restart: unless-stopped
    environment:
      - JELLYFIN_URL=http://jellyfin:8096
      - URL_BASE_PATH=/swipe       # must match the --build-arg value exactly
    volumes:
      - ./swiparr-data:/app/data
    ports:
      - 4321:4321

URL_BASE_PATH must also be passed as a runtime environment variable so the app generates correct internal links and auth redirects. It must match the --build-arg value exactly.

Step 3 — Configure your reverse proxy

Forward requests for the subpath to the container. The app handles stripping the prefix internally — do not strip it in the proxy.

Nginx:

location /swipe {
    proxy_pass http://swiparr:4321;
    proxy_set_header Host $host;
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    proxy_set_header X-Forwarded-Proto $scheme;
}

Traefik (Docker labels):

labels:
  - "traefik.enable=true"
  - "traefik.http.routers.swiparr.rule=Host(`jellyfin.example.com`) && PathPrefix(`/swipe`)"
  - "traefik.http.routers.swiparr.tls=true"

Caddy:

handle /swipe* {
    reverse_proxy swiparr:4321
}

Reverse Proxy Configuration (root path)

Nginx Example:

location / {
    proxy_pass http://swiparr:4321;
    proxy_set_header Host $host;
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    proxy_set_header X-Forwarded-Proto $scheme;
}

Required Headers:

  • Host - Required for authentication
  • X-Forwarded-For - Client IP for logging
  • X-Forwarded-Proto - Protocol detection

Volume Management

volumes:
  - ./data:/app/data          # Database & cache
  - ./logs:/app/logs          # Optional: Persist logs

Environment File

Use --env-file .env with Docker for cleaner configuration management.


📚 Additional Resources

  • AGENTS.md - Developer guide and code standards (for contributors)
  • GitHub Releases - Detailed changelog for each version

📄 License

MIT License - See LICENSE file for details

You're free to use, modify, and distribute Swiparr. Commercial use is permitted.


Made with ❤️ and late nights

🌐 Swiparr Global⭐ GitHub Repo💬 Community

Install Swiparr on Unraid in a few clicks.

Find Swiparr 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 Swiparr Review the template variables and paths Click Install

Requirements


        Requires a separate Jellyfin instance.
    

Details

Repository
ghcr.io/m3sserstudi0s/swiparr:latest
Last Updated2026-07-15
First Seen2026-02-16

Runtime arguments

Web UI
http://[IP]:[PORT:4321]/
Network
bridge
Privileged
false

Template configuration

Web UI PortPorttcp

Container Port: 4321

Target
4321
Default
4321
Value
4321
Jellyfin - URLVariable

Internal URL to your Jellyfin server.

Target
JELLYFIN_URL
Default
http://IP_ADDRESS:8096
Value
http://IP_ADDRESS:8096
Jellyfin - Public URLVariable

(Optional) Public URL to your Jellyfin server. Defaults to the internal URL if not set.

Target
JELLYFIN_PUBLIC_URL
Jellyfin - Use WatchlistVariable

Set to true to use Watchlist instead of Favorites

Target
JELLYFIN_USE_WATCHLIST
Default
false|true
Use Secure CookiesVariable

Set to true if accessing Swiparr over HTTPS

Target
USE_SECURE_COOKIES
Default
false|true
Admin UsernameVariable

(Optional) A Jellyfin username that will always have admin privileges.

Target
ADMIN_USERNAME
Auth SecretVariable

Random string (min 32 chars). Generated automatically if not set.

Target
AUTH_SECRET
Internal Database PathVariable

Internal path to database. Not recommended to change.

Target
DATABASE_URL
Default
file:/app/data/swiparr.db
Value
file:/app/data/swiparr.db
Internal PortVariable

Internal port Swiparr listens on. Not recommended to change.

Target
PORT
Default
4321
Value
4321
Internal HostVariable

Internal host Swiparr binds to. Not recommended to change.

Target
HOSTNAME
Default
0.0.0.0
Value
0.0.0.0
X-Frame OptionsVariable

X-Frame-Options header. Set to DISABLED to remove.

Target
X_FRAME_OPTIONS
Default
DENY
Value
DENY
CSP Frame AncestorsVariable

Content-Security-Policy frame-ancestors directive.

Target
CSP_FRAME_ANCESTORS
Default
none
Value
none
App DataPath

Path to the application data folder

Target
/app/data
Default
/mnt/user/appdata/swiparr/data
Value
/mnt/user/appdata/swiparr/data
PUIDVariable
Default
099
Value
099
PGIDVariable
Default
100
Value
100