emby-watchparty

emby-watchparty

Docker app from Mahesvara's Toolshed

Overview

A synchronized watch party application for Emby media servers. Vue 3 SPA + FastAPI backend with per-user HLS transcodes, late-joiner voting, in-party admin panel, and drift correction. All streaming is proxied through the container so your Emby server can stay on your internal network. Only boot-essential values are set here; everything else (logging, security, rate limits, late-join vote, static session, force-transcode, etc.) is managed live via the /admin panel and persisted to config.json.

Emby Watch Party

License: MIT Python 3.12+ FastAPI Vue 3 Docker Image GitHub release GitHub stars Discord

A synchronized watch-party frontend for Emby: watch together in sync while your Emby server stays on your internal network. Any member with Emby credentials can become the room's host; other viewers join as spectators without an account.


Table of contents


Support development

If Emby Watch Party has saved you from the hell of trying to coordinate "3, 2, 1, play" over Discord, consider buying me a coffee:

ko-fi.com/jedziah

Every tip helps fund the hardware and the late nights reverse-engineering Emby's HLS pipeline.


Special thanks

Special thanks to QuackMasterDan for his dedication in testing and providing valuable feedback throughout development.

Thanks to wlowen and JeslynMcKenzie for testing, detailed bug reports, and providing mediainfo that helped track down the HEVC transcoding issues.

Thanks to @stealthydruid and @xyxxyxxy for the bug reports and feature requests on the Discord support server that shaped the late-stage 2.0 betas: APP_PREFIX healthcheck, A-Z library jump bar, image thumbnail sizing, resume from last position, jump-to-timestamp input, and the seek-bar tooltip thinking that got us there.


Discord

https://discord.gg/RWUpxq9xsA


Documentation

Features

  • Secure proxy architecture: your Emby server stays on the local network; only the WatchParty app is exposed
  • FastAPI + Vue 3 + TypeScript stack: async end-to-end, typed OpenAPI docs at /docs and /redoc, single uvicorn process serves backend + compiled SPA
  • Real-time sync with drift correction: selector-authoritative play/pause/seek against a party clock, keeping independent streams aligned
  • Late-joiner voting flow: existing users vote to admit mid-playback joiners; a passing vote restarts on a PTS-aligned segment 0 so everyone lands together
  • Host-provider auth model: UNLOCKED / PLAYING-ONLY / LOCKED lock states around the Emby-authenticated host, with a grace window for quick refreshes
  • Per-user transcodes: every viewer gets their own Emby PlaySessionId, so audio, subtitles, quality, and version are personal without disrupting the room
  • Multi-version playback: host-locked picker for items with multiple MediaSources (theatrical vs director's cut, mp4 vs mkv, 1080p vs 4K HDR)
  • Bitrate-granular quality menu: mirrors Emby's own per-resolution table, with admin-side toggles for exposed resolutions and bitrates
  • Unified subtitles: text subs via side-channel proxy, image (PGS) subs burned in per-user, both surfaced in one dropdown
  • Resume, jump-to-timestamp, and binge auto-advance: Continue Watching prompt from Emby UserData, absolute-timestamp seek input, and end-of-episode countdown with room-wide Cancel
  • Rooms, chat, and identity: 5-character party codes, live chat with a mobile slide-over, custom avatars (upload / Gravatar / monsterid fallback) recoverable via 3-word codes
  • Refined Cyber UI: cyan/magenta/violet palette, glass surfaces, chip/pill controls, animated LIVE badge, iOS-style A-Z library jump bar
  • Reverse-proxy ready: APP_PREFIX wired end-to-end so one Docker image sits behind any subpath (https://example.com/watchparty/...)
  • Admin panel with hot-reload: runtime settings editable from /admin as an in-party modal, .env reserved for boot-only keys
  • Reload-as-rejoin + healthchecks: refresh in an active party rejoins via persistent client_id; /api/health liveness for Docker/Kubernetes probes; rsyslog-style logging with rotation

Browser compatibility

Emby Watch Party works best with the following browsers:

Desktop

  • Chrome - full support (recommended)
  • Edge - full support (recommended)
  • Firefox - full support
  • Safari - full support
  • Brave - full support

Mobile

  • Safari (iOS) - full support with subtitles (recommended for iOS)
  • Chrome (Android) - full support (recommended for Android)
  • Brave (iOS) - video playback works, but subtitles do not appear in fullscreen mode
    • Workaround: use Safari on iOS if you need subtitle support

Known issues

  • Brave browser on iOS: subtitles work in normal view but disappear when entering fullscreen mode. This is a limitation of how Brave handles native video controls on iOS. Safari is recommended for iOS users who need subtitle support.

Setup

Prerequisites

  • An Emby server (can be on local/internal network only; does not need to be exposed to the internet - the app acts as a secure proxy)
  • An Emby admin API key (per-user Emby credentials are supplied at runtime via the in-app "Login to Become Host" flow, never in .env)
  • The app must be reachable by your remote viewers - use a VPN such as Tailscale or Hamachi if you cannot port-forward
  • For Docker installs (recommended): Docker 20.10+ (and optionally Docker Compose v2)
  • For manual installs:
    • Python 3.12 or higher (matches the runtime used in the official image)
    • Node.js 20.19+ (or 22.12+) and npm - the Vue 3 frontend is a Vite build that must be produced before FastAPI can serve it

Manual installation

Docker (below) is the easier and recommended path - it bundles the frontend build and Python runtime for you. Only use manual installation if you specifically need to run against a local Python environment.

  1. Build the frontend (Vite emits into backend/static/, which FastAPI serves):

    cd frontend
    npm ci
    npm run build
    cd ..
    
  2. Install the Python backend dependencies:

    pip install -r requirements.txt
    
  3. Configure your settings. Copy .env.example to .env and fill in at least EMBY_SERVER_URL, EMBY_API_KEY, and SESSION_SECRET (generate with openssl rand -hex 32):

    cp .env.example .env
    

    Only boot-essential settings live in .env - see .env.example for the full annotated list (bind/port, APP_PREFIX, SESSION_SECRET, SESSION_COOKIE_SECURE, CORS_ALLOWED_ORIGINS, EMBY_SERVER_URL, EMBY_API_KEY). All other runtime options (logging, rate limits, late-join vote, FORCE_TRANSCODE, REQUIRE_LOGIN, etc.) are managed live from the Admin Panel at /admin and persisted to config.json.

  4. Run the application from the repository root:

    python -m backend.app
    
  5. Open your browser and navigate to:

    http://localhost:5000
    

    (If you set APP_PREFIX=/watchparty, the app is served at http://localhost:5000/watchparty/ instead.)

Docker installation

The pre-built multi-arch image is published to GitHub Container Registry:

docker pull ghcr.io/oratorian/emby-watchparty:latest

Recommended: use the compose example. Copy docker-compose.yml.example to docker-compose.yml, copy .env.example to .env and fill in your Emby URL + API key, then:

# One-time: pre-create config.json so Docker does not create it as a
# directory on first `up`. Skip this and the backend will crash trying
# to write its settings.
touch config.json

docker compose up -d

The compose file mounts everything correctly out of the box. If you prefer docker run, the equivalent invocation is:

touch config.json

docker run -d \
  --name emby-watchparty \
  -p 5000:5000 \
  --env-file .env \
  -v "$(pwd)/data:/app/data" \
  -v "$(pwd)/images/avatars:/app/images/avatars" \
  -v "$(pwd)/config.json:/app/config.json" \
  -v "$(pwd)/logs:/app/logs" \
  ghcr.io/oratorian/emby-watchparty:latest

Required mounts (avatars and runtime config will be wiped on container recreation without them):

Host path Container path Purpose
./data /app/data SQLite avatar DB (avatars.db)
./images/avatars /app/images/avatars Uploaded avatar image files
./config.json /app/config.json Runtime admin settings (edited via /admin)

Optional mount:

Host path Container path Purpose
./logs /app/logs Application logs (only written when LOG_TO_FILE is enabled in /admin; console-only by default)

SESSION_SECRET is new in 2.0.0-beta18: it's the signing key for the party-bound session cookie. Set it once (openssl rand -hex 32) and leave the value stable across restarts and across every uvicorn worker. When unset, an ephemeral key is generated at boot with a loud warning - every restart kicks all users out of their party, and multi-worker deploys become non-deterministic per request. Pair with SESSION_COOKIE_SECURE=true on any HTTPS deployment and pin CORS_ALLOWED_ORIGINS to your real origin(s) (the historical * default remains for backwards compat). Full block in .env.example.

Config split (2.0): .env is boot-only (~6 keys: bind/port, Emby URL/key, session hardening). All other settings - including LOG_TO_FILE, transcoding options, feature toggles - live in config.json and are edited from the admin panel at /admin. Do not try to set them as environment variables; they will be ignored.

Usage

Creating a watch party

The default (REQUIRE_LOGIN=false):

  1. Click "Create Party" on the home page (no login required)
  2. Share the party code or URL with your friends
  3. Inside the party, click "Login to Become Host" with your Emby credentials -- this unlocks the library for everyone in the room. Any party member with an Emby account can do this; spectators never see a login prompt.
  4. Browse the library and select a video
  5. Everyone in the room will be synchronized

With REQUIRE_LOGIN=true (set from the admin panel):

  1. Click "Create Party" -- you will be prompted for Emby credentials
  2. The creator becomes host atomically; the party starts UNLOCKED
  3. Share the code; spectators join with no login prompt
  4. Browse, pick, watch

In both modes, when the host disconnects mid-playback the in-flight video keeps streaming until it ends naturally (PLAYING-ONLY state). The library re-locks immediately; any member can click "Login to Become Host" to unlock it again.

Joining a watch party

  1. Click "Join Watch Party" on the home page (or open a shared URL)
  2. Enter the party code if needed
  3. Enter your username
  4. Start watching together!

If you join before a video has been selected, you land directly in the party. If you join while a video is already playing, the existing users will see a vote modal asking whether to restart the video from the beginning so you can join in sync. See the project wiki for full details of the late-joiner vote flow.

In-party controls

  • Browse library: use the sidebar to browse your Emby libraries, movies, and TV shows
  • Select video: click on any video to start watching it with the group
  • Video controls: any user can play, pause, or seek - all users will sync
  • Audio, subtitles, quality: each user can pick their own settings independently. If the party is paused, the change is silent; if the party is playing, everyone briefly pauses while the new stream loads so you do not desync
  • Chat: use the chat box at the bottom to communicate with other viewers
  • Leave: click the "Leave" button to exit the watch party

Configuration

Configuration is split into two tiers:

  1. Boot-essential settings live in .env and require a server restart to change. Copy .env.example to .env and set these before starting the service.
  2. Runtime settings are editable from the admin panel at /admin (Emby administrator credentials required) and are hot-reloadable -- no restart needed.

.env

Boot-essential, restart required.

Variable Description Default
Application
WATCH_PARTY_BIND IP address to bind to 0.0.0.0
WATCH_PARTY_PORT Port to run on 5000
APP_PREFIX URL prefix for reverse proxy deployments (e.g. /watchparty) (empty)
SESSION_EXPIRY Session expiry in seconds 86400
Session cookie (new in 2.0.0-beta18)
SESSION_SECRET Signing key for the party-bound session cookie. Generate ONCE with openssl rand -hex 32 and leave it. Empty = ephemeral random per boot with a loud warning (every restart kicks users out; multi-worker deploys are non-deterministic). (generated)
SESSION_COOKIE_SECURE When true, the session cookie carries the Secure flag (HTTPS-only). Set true in every deployment behind TLS; leave false for http://localhost dev. false
Socket.IO CORS (new in 2.0.0-beta18)
CORS_ALLOWED_ORIGINS Comma-separated origin allowlist for the Socket.IO server (https://a.example.com,https://b.example.com). * accepts any origin (historical default). Pin to your real origin(s) in production. *
Emby server
EMBY_SERVER_URL Your Emby server URL http://localhost:8096
EMBY_API_KEY Emby API key (server admin key) (required)

REQUIRE_LOGIN was previously here. It now lives in the admin panel as a runtime, hot-reloadable setting; see the host-provider authentication model in Architecture for the full semantics.

Admin panel

Runtime, hot-reloadable. All of the following settings are editable at /admin. See the project wiki for a walkthrough.

Logging

Setting Description Default
Log level Application log verbosity (DEBUG, INFO, WARNING, ERROR) INFO
Console log level Terminal output verbosity WARNING
Log to file Write logs to disk true
Log file Path to log file logs/emby-watchparty.log
Log format rsyslog or standard rsyslog
Max log size (MB) Rotation threshold 10

Security

Setting Description Default
Max users per party 0 = unlimited 0
HLS token validation Prevent direct stream access bypass true
HLS token expiry (s) Token lifetime 86400
Rate limiting Enable API rate limiting true
Party creation limit Max per IP 5 per hour
API rate limit Max per IP 1000 per minute

Session

Setting Description Default
Static session mode Auto-create a fixed party on startup false
Static session ID Party code when static mode is enabled PARTY

Late join vote (new in 2.0)

Setting Description Default
Enable late join vote Require a majority vote to admit users who join mid-playback true
Vote timeout (s) Seconds before the selector tiebreak kicks in 20
Post-vote cooldown (s) Delay after a failed vote before another join attempt is allowed (0 disables) 30

Architecture

Backend (FastAPI + python-socketio)

  • FastAPI: async REST API, dependency-injected components, Pydantic schemas for every request/response body (backend/src/schemas.py).
  • python-socketio AsyncServer (ASGI mode) mounted under APP_PREFIX/socket.io: WebSocket-based real-time sync, hardened with a 128 KiB frame cap and tightened ping timings.
  • httpx: async HLS proxy - the backend streams .m3u8/.ts/.vtt segments from Emby on the user's behalf so the Emby server never sees the public internet.
  • uvicorn ASGI worker, bound to WATCH_PARTY_BIND:WATCH_PARTY_PORT.
  • HLS.js-compatible playlists: token-gated (HLSTokenManager) short-lived URLs prevent segment scraping.

Frontend (Vue 3 + TypeScript)

  • Vue 3 SPA authored in TypeScript, bundled with Vite (base ./ for prefix-agnostic asset resolution).
  • Pinia stores for auth, party, playback, and socket state; Vue Router for /party/:code, /admin, and landing routes.
  • socket.io-client for the sync channel, axios for REST, hls.js for adaptive playback.

Per-user transcode model

Each viewer gets their own PlaySessionId and a fresh Emby transcode session built by StreamBuilder. Seeks, quality changes, and disconnects only affect the individual user's stream - the party's shared clock stays authoritative, and Emby's stop_active_encodings is called on every disconnect to reap the transcode.

Host-provider authentication

There is no application user database. Any party member can promote themselves to host by authenticating with Emby credentials from inside the party (POST /api/auth/login). The resulting AccessToken lives in RAM only on the PartyManager party record and is never persisted or sent to clients. When the host disconnects, a 5-second grace timer starts; a refresh/reconnect during that window silently reclaims host. If the timer expires, the party transitions through a three-state lock:

  • UNLOCKED - host present, all playback and library browsing allowed.
  • PLAYING-ONLY - host gone mid-playback; the in-flight token is retained so viewers can finish the current video, but no new playback or seeks may start.
  • LOCKED - host gone with no active video; token cleared, party waits for someone to log in as the new host.

Key components

  • PartyManager - party lifecycle, sid <-> client_id mapping, host state machine, ready-check and late-join vote bookkeeping.
  • EmbyClient - thin async wrapper around Emby's REST API (auth, items, playback reporting, encoding teardown).
  • HLSTokenManager - mints and validates short-lived per-user HLS URL tokens.
  • StreamBuilder - assembles per-user transcode URLs, honouring the FORCE_TRANSCODE admin toggle.
  • AvatarStore - SQLite-backed avatar metadata plus a sibling images directory (both Docker-mountable).

Deployment

Ships as a single multi-stage Docker image: a Node stage runs npm run build to produce the Vue bundle, then a Python stage installs backend dependencies and copies the built static/ into the FastAPI app. One container, one port, one process - uvicorn serves the API, the SPA, and the WebSocket mount under the configurable APP_PREFIX.

API endpoints

Emby Watch Party 2.0 ships a FastAPI backend (REST + OpenAPI) and a Socket.IO real-time surface. Rather than duplicate the full spec here, the canonical references are:

  • REST / OpenAPI: interactive docs at /docs (Swagger UI) and /redoc when the backend is running.
  • Socket.IO: docs/SOCKET_API.md - every inbound / outbound event, payload shapes, and the multi-step flows.

REST API (representative endpoints)

The FastAPI app is composed of nine routers under backend/src/routers/. A small sample:

  • auth - POST /api/auth/login (Emby login, host-claim), POST /api/auth/logout
  • party - POST /api/party/create, POST /api/party/{id}/join, GET /api/party/{id}/info
  • library - GET /api/libraries, GET /api/items?parentId=..., GET /api/item/{item_id}
  • media - GET /api/media/{item_id}/streams, GET /api/media/{item_id}/versions
  • hls - GET /api/hls/{session}/master.m3u8, segment + subtitle proxy passthrough
  • quality - GET /api/quality/profiles, GET /api/quality/default
  • avatar - POST /api/avatar/upload, GET /api/avatar/{uuid}
  • admin - GET/PUT /api/admin/config, POST /api/admin/party/{id}/dissolve
  • health - GET /api/health, GET /api/version

See /docs for the full parameter, response, and auth-scope details.

WebSocket events (Socket.IO)

Mounted under ${APP_PREFIX}/socket.io. Events group into these categories:

  • Connection lifecycle - connect / disconnect / connected, transparent reconnect via stable client_id.
  • Party join / leave / vote - join_party, leave_party, user_joined / user_left, plus the late-joiner vote (join_vote, join_vote_started, join_vote_update, join_vote_resolved, join_rejected).
  • Host state - host_changed, host_left, host_reclaimed, three-state lock (UNLOCKED / PLAYING-ONLY / LOCKED).
  • Playback - select_video, stop_video, change_streams (per-user), play, pause, seek, video_selected, streams_changed, video_stopped, video_ended.
  • Sync coordination - stream_ready, ready_check_update, all_ready, force_pause_before_seek, heartbeat + drift_correction, report_progress.
  • Chat / UI - chat_message, toggle_library, update_avatar + members_update, sync_state.
  • Binge-watch / auto-advance - set_binge_watch_active, binge_watch_state_changed, auto_advance_pending, auto_advance_cancel(led), auto_advance_fired, binge_finished.
  • Admin / errors - party_dissolved, error.

Full payloads, auth requirements, and flow diagrams live in docs/SOCKET_API.md.

Troubleshooting

Common issues and fixes are catalogued on the wiki: Troubleshooting.

Quick checks before opening an issue:

  • App unreachable. Confirm the backend is bound on WATCH_PARTY_BIND/WATCH_PARTY_PORT and that clients can reach it. Behind a reverse proxy, verify APP_PREFIX matches the mount path and that WebSocket upgrades are forwarded.
  • Can't browse or play media. The admin EMBY_API_KEY in .env must be valid, and the Emby server (EMBY_SERVER_URL) must be reachable from the backend. To play, someone in the party has to click Login to Become Host and supply their own Emby credentials - nothing plays until a host provider is attached.
  • Playback works, sync doesn't. Confirm the Socket.IO transport isn't being downgraded or blocked by an intermediate proxy, and that CORS_ALLOWED_ORIGINS includes your real origin.
  • Session cookie rejected / kicked out on every restart. SESSION_SECRET must be set to a stable value (openssl rand -hex 32). An unset secret regenerates on boot and invalidates every existing cookie; with --workers >1 each worker signs differently.
  • Logs. Logging goes to stdout by default. File logging is opt-in from /admin -> Logging; the log path shown there is where to look once enabled.

Security notes

  • Proxy architecture. The Emby server stays on your internal network. All HLS segments and playlists are pulled by the backend and re-served to clients, so browsers never talk to Emby directly.
  • Credential model.
    • .env holds only the admin EMBY_API_KEY, used for library browsing and administrative Emby calls. Do not commit .env.
    • Per-user Emby credentials are never persisted. Any party member can click Login to Become Host in-app and authenticate against Emby; the resulting AccessToken lives in memory as that party's host provider and is used to open per-user transcode sessions. Tokens are discarded when the host provider changes or the party ends.
  • Session secret. SESSION_SECRET signs the party-bound session cookie and must be stable across restarts and across every uvicorn worker - generate once with openssl rand -hex 32. An unset secret produces an ephemeral key at boot (loud warning), which invalidates cookies on every restart and produces non-deterministic sessions under --workers >1.
  • HLS URLs. Since beta18, HLS playlist and segment URLs served to the browser no longer carry the admin EMBY_API_KEY as a query parameter. Access is gated by a per-stream HLS token bound to the session cookie; the API key stays server-side.
  • Party codes are generated with cryptographically secure random tokens.
  • Built-in controls. HLS token validation, per-IP rate limiting, and configurable party size limits are on by default; tune them in /admin -> Security.
  • For internet-facing deployments, terminate TLS at a reverse proxy (nginx, Caddy, Traefik), set SESSION_COOKIE_SECURE=true, and pin CORS_ALLOWED_ORIGINS to your real origin(s) instead of *.

License

MIT License - feel free to modify and use as you wish.

Contributing

Contributions welcome. See CONTRIBUTING.md for branch conventions, PR flow, and coding guidelines. Issues and feature requests go in GitHub Issues.

Wiki edits are open - if you deploy on hardware or a platform not yet documented, please add your findings.

Acknowledgments

  • Integrates with Emby Media Server
  • UI design: Refined Cyber by Christian Gillinger
  • The community testers on Discord who lived through every beta and kept filing the bug reports that made 2.0 what it is

Educational use notice

This project is intended for educational purposes and private use only. Please ensure you use this responsibly and in compliance with your Emby server's terms of service and applicable copyright laws.

Install emby-watchparty on Unraid in a few clicks.

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

Requirements

A running Emby server reachable from this container and an admin-level Emby API key (Emby Dashboard > Advanced > API Keys). Per-user authentication is an in-app action -- any party member clicks "Login to Become Host" and supplies their own Emby credentials, so no long-lived user creds are stored on disk. Emby Premiere is required on the Emby side for hardware-accelerated transcoding (QSV, NVENC, VAAPI).

Related apps

Details

Repository
ghcr.io/oratorian/emby-watchparty:latest
Last Updated2026-07-18
First Seen2026-05-04

Runtime arguments

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

Template configuration

Web UI PortPorttcp

Host port for the emby-watchparty web interface. Must match WATCH_PARTY_PORT below.

Target
5000
Default
5000
Value
5000
Emby Server URLVariable

Full URL to your Emby server, e.g. http://192.168.1.100:8096. Reachable from inside this container.

Target
EMBY_SERVER_URL
Default
http://192.168.1.x:8096
Value
http://192.168.1.x:8096
Emby API KeyVariable

Admin Emby API key. Generate one in Emby Dashboard &gt; Advanced &gt; API Keys. Per-user credentials are NOT set here -- users log in from inside the party UI.

Target
EMBY_API_KEY
Session SecretVariable

Signing key for the party-bound session cookie. Generate ONCE with `openssl rand -hex 32` and leave it -- must persist across restarts and across every uvicorn worker. If left at the default, an ephemeral key is generated at boot with a loud warning; fine for local dev, catastrophic in production (every restart kicks everyone out).

Target
SESSION_SECRET
Default
change-me-openssl-rand-hex-32
Value
change-me-openssl-rand-hex-32
Session Cookie SecureVariable

When `true`, the session cookie carries the Secure flag so browsers only send it over HTTPS. Set `true` for every deployment behind TLS; leave `false` for local http://localhost dev (setting it true on an http origin locks users out).

Target
SESSION_COOKIE_SECURE
Default
false
Value
false
Bind AddressVariable

Interface the app listens on inside the container. 0.0.0.0 listens on all interfaces (typical for Docker); rarely needs changing.

Target
WATCH_PARTY_BIND
Default
0.0.0.0
Value
0.0.0.0
Container PortVariable

Port the app binds inside the container. Must match the container-side of the Web UI Port mapping above.

Target
WATCH_PARTY_PORT
Default
5000
Value
5000
App PrefixVariable

URL prefix for reverse-proxy deployments behind a subpath (e.g. /watchparty). Leave empty for root-of-domain deployments. Backend routers, Socket.IO, /assets, and the SPA all honour the prefix at runtime -- no rebuild required.

Target
APP_PREFIX
CORS Allowed OriginsVariable

Comma-separated origin allowlist for the Socket.IO server. `*` accepts XHR-poll handshakes from any origin -- convenient for testing but pair with per-IP throttling. Pin to your real origin(s) for a proper deployment, e.g. `https://watchparty.example.com,https://staging.example.com`.

Target
CORS_ALLOWED_ORIGINS
Default
*
Value
*
Session Expiry (seconds)Variable

Session cookie lifetime in seconds. Default 86400 (24 hours).

Target
SESSION_EXPIRY
Default
86400
Value
86400
Avatar DatabasePathrw

Persists the avatars.db SQLite database. Without this mount, every container recreate wipes every member's chosen avatar.

Target
/app/data
Default
/mnt/user/appdata/emby-watchparty/data
Value
/mnt/user/appdata/emby-watchparty/data
Avatar ImagesPathrw

Persists uploaded avatar image files. Paired with the Avatar Database mount above.

Target
/app/images/avatars
Default
/mnt/user/appdata/emby-watchparty/avatars
Value
/mnt/user/appdata/emby-watchparty/avatars
Admin Config (config.json)Pathrw

Persists the runtime admin config (edited via /admin) across container recreates. IMPORTANT: `touch config.json` on the host BEFORE the first start, otherwise Docker silently creates it as an empty DIRECTORY and the backend fails to write.

Target
/app/config.json
Default
/mnt/user/appdata/emby-watchparty/config.json
Value
/mnt/user/appdata/emby-watchparty/config.json
Logs (optional)Pathrw

Optional: persist application logs to disk. Only writes when Log To File is enabled in the /admin panel (default: console-only).

Target
/app/logs
Default
/mnt/user/appdata/emby-watchparty/logs
Value
/mnt/user/appdata/emby-watchparty/logs