Skip to content
Pascal van den Berg edited this page Oct 9, 2025 · 37 revisions

CrossWatch is a lightweight synchronization engine that keeps your Plex, Jellyfin, Simkl, and Trakt in sync. It runs locally with a clean web UI to link accounts, configure sync pairs, run them manually or on schedule, and track stats/history. It also fully replaces my previous project Plex2SIMKL, with a more modular architecture and broader multi-provider support.

CrossWatch aims to become a one-for-all synchronization system for locally hosted environments. Its modular architecture allows new providers to be added easily. This approach keeps the system maintainable, testable, and easy to extend as new platforms emerge.

⚠️ EARLY DEVELOPMENT
This project is unstable and may break.
What works (mostly): most Plex and Trakt features, including live scrobbling (Plex → Trakt).
Still maturing: SIMKL and Jellyfin remain in active testing.
Always back up your data before use.

🚀 Features

  • Sync watchlists (one-/two-way)
  • Live scrobbling (Plex → Trakt)
  • Sync ratings (one-/two-way)
  • Sync watch history (one-/two-way)
  • Sync playlists (one-/two-way — disabled)
  • Simple web UI — JSON state
  • TMDb metadata & posters
  • Stats, history, live logs
  • Headless scheduled runs
  • Trackers: SIMKL  Trakt
  • Media servers: Plex  Jellyfin

🐳 Run as Container

docker run -d   --name crosswatch   -p 8787:8787   -v /path/to/config:/config   -e TZ=Europe/Amsterdam   ghcr.io/cenodude/crosswatch:latest

or

# docker-compose.yml
services:
  crosswatch:
    image: ghcr.io/cenodude/crosswatch:latest
    container_name: crosswatch
    ports:
      - "8787:8787"          # host:container
    environment:
      - TZ=Europe/Amsterdam
    volumes:
      - /path/to/config:/config
    restart: unless-stopped

The container exposes the web UI at:
👉 http://localhost:8787

By default CONFIG_BASE will be /config inside the container.
Your config.json, state.json, statistics.json, etc. will all be stored there.


🧩 Architecture

  • FastAPI backend (crosswatch.py) at port 8787
  • FastAPI frondent (FastAPI.py)
  • Vanilla JS/CSS UI served from /assets/
  • Pluggable providers:
    • auth (Plex, Jellyfin, SIMKL, TRAKT)
    • sync (PLEX ⇄ Jellyfin ⇄ SIMKL ⇄ TRAKT)
    • metadata (TMDb enrichment)
  • All state/config stored as JSON in CONFIG_BASE

⚠️ Limitations

CrossWatch is intentionally lightweight: no database, JSON files only. That keeps it simple but caps scale.

  • Not for very large libraries. JSON state and provider rate limits become the bottleneck.
  • Soft size guidance (varies by hardware; SSD + 2 GB RAM recommended):
    • Watchlist: stable up to ~2,500 items total across providers.
    • History (plays): stable up to ~10,000 play events total.
    • Ratings: stable up to ~10,000 ratings total.
  • Above these ranges you may see slower planning, large state files, and long first-run times.
  • Heavy backfills (full history/ratings) can take hours. Prefer incremental windows when possible.

TL;DR: small/medium home setups = fine. Massive media hoards = use with caution.


🐛 Known Issues

  • Sync progress bar can behave oddly due to real-time updates. Use CTRL + F5 to hard refresh.
  • Stale UI data: sometimes sections don’t update instantly. CTRL + F5 helps.
  • Container can freeze on heavy backfills or low-memory hosts. Restart the container.
  • SIMKL & Jellyfin are still maturing; edge cases are expected.
  • Two-way modes are provider-capability dependent; conflict policies are evolving.

Next steps

Clone this wiki locally