unsubbed.co

WatchState

WatchState lets you sync media play states across Plex, Jellyfin, and Emby entirely on your own server.

Self-hosted media state sync, honestly reviewed. If you run more than one media server and hate losing your place, this is the article for you.

TL;DR

  • What it is: A lightweight, MIT-licensed Docker container that syncs watch history and play progress between Plex, Jellyfin, and Emby — without touching any third-party cloud service [README].
  • Who it’s for: Self-hosters who run more than one media server (or are evaluating whether to switch from Plex to Jellyfin) and don’t want to lose their “watched” status in the process [1].
  • Cost savings: Plex Pass costs $39.99/year or $4.99/month. Trakt VIP runs around $3/month. WatchState itself is free. The savings are modest, but the value is avoiding vendor lock-in on your own watch history.
  • Key strength: Single source of truth model — one local database that treats all your media backends as equal participants. Bidirectional or one-way sync, webhooks, scheduled tasks, and it runs rootless in Docker [README][1].
  • Key weakness: Narrow use case by design — useless if you only run one media server. A significant Plex API change in March 2026 broke support for external (invited) users and is not recoverable within WatchState’s current architecture [README]. Small project: 1,333 stars, 7 contributors.

What is WatchState

WatchState is a self-hosted Docker container that does one thing: keeps your watch state synchronized across Plex, Jellyfin, and Emby media servers. If you finish a movie on Plex, WatchState marks it watched on Jellyfin. If you watch three episodes of a show on Jellyfin, Plex knows about it too. No manual syncing, no third-party service, no cloud account involved.

The project describes itself as a tool to “sync your backends users play state without relying on third party services” — which is both accurate and complete [README]. There’s no marketing inflation here. The README is written by a developer for developers, and the feature list is exactly what you’d expect: multi-user support, backup and restore in a portable format, webhook events from media backends, scheduled sync tasks, parity checks to verify that your servers agree on what you’ve watched, and detection of unmatched or mismatched media items.

The WebUI is accessible at port 8080 and handles initial setup, backend configuration, and ongoing management. The project has 71 releases, is written in PHP, and ships as a rootless container — the user permission setup is the most common friction point for new installs [README].

At 1,333 GitHub stars and 7 contributors, this is a small project. It’s not backed by a company, there’s no cloud tier, and there’s no commercial version. It’s a tool one person (arabcoders) built to scratch a real itch, which means the scope is narrow and the maintenance model is one-maintainer-dependent.


Why people choose it

The scenario is specific: you self-host Plex and are curious whether Jellyfin is better, or you’ve decided to run both in parallel, or you’re actively migrating. The problem is that watch history doesn’t transfer between them, so your testing is polluted by having to remember what you’ve already seen.

The XDA Developers review [1] frames this clearly: “Your Jellyfin session thinks you’re halfway through a movie while Plex believes you haven’t even started watching it.” That’s the exact annoyance WatchState eliminates.

What makes WatchState the right tool rather than a homegrown script is the “single source of truth” approach. It maintains one local database representing authoritative watch state and propagates changes outward — you choose whether that’s bidirectional or one-way [1]. For someone keeping Plex as primary and running Jellyfin as an evaluation, one-way sync keeps Jellyfin current without any messy conflict resolution. For someone fully migrated to Jellyfin but keeping Plex for family members, bidirectional sync means everyone’s progress stays consistent.

The deployment model is also practical. Because WatchState only needs API access to each server, it doesn’t have to run on the same machine as either backend. The XDA review describes running Plex on a VPS and WatchState on a home NAS, syncing to a local Jellyfin instance — and it works without any special configuration [1]. You just need the server URLs and credentials.

WatchState also showed up in the Unraid April 2025 digest as a featured community application under their monthly spotlight content — “Set Up & Configure WatchState on Unraid” [2]. The Unraid Community Applications plugin ships it preconfigured, which is the closest thing to an endorsement from the Unraid ecosystem.


Features

Based on the README and [1]:

Core sync engine:

  • Bidirectional or one-way sync between Plex, Jellyfin, and Emby [README]
  • Watch progress sync (not just “watched/unwatched” — also your position in partially-watched content) [README][1]
  • Multi-user support with sub-user handling [README]
  • Webhook receiver: media backends push events to WatchState on play, pause, stop, and finish [README]
  • Scheduled sync tasks for polling-based backends [README]
  • Single local database as source of truth [1]

Data integrity tools:

  • Parity checks: verify all your media servers agree on watched status [README]
  • Unmatched/mismatched item detection: find media that didn’t sync correctly because metadata didn’t align [README]
  • Stale reference check: identify backends pointing to files that no longer exist [README]
  • Backend metadata search [README]

Backup and restore:

  • Export play state to a portable format [README]
  • Restore from backup — useful when migrating servers or rebuilding a Jellyfin instance [README]

Deployment:

  • Docker and Docker Compose [README]
  • Rootless container (runs as your user, not root) [README]
  • WebUI at port 8080 for setup and management [README]
  • Unraid Community Applications preconfigured [README][2]
  • Podman supported (with user mapping caveat) [README]

Plex-specific:

  • “Login with Plex” — no manual token extraction required for home/managed users [1][README]
  • Removed in March 2026: support for Plex external/invited users due to Plex API changes that prevent generating access tokens for those account types [README]

Pricing: SaaS vs self-hosted math

WatchState has no pricing tiers. It’s MIT-licensed software you run yourself. The relevant cost comparison is what you’re replacing or avoiding.

Plex Pass: If you run Plex, you’ve likely already paid or are considering it. Plex Pass is $4.99/month, $39.99/year, or $119.99 lifetime. It includes watch history sync across Plex devices and apps — but only within the Plex ecosystem. It does nothing for Jellyfin or Emby. If you’ve paid for Plex Pass and want to evaluate Jellyfin without losing your history, WatchState is the bridge.

Trakt.tv: The common third-party solution for cross-platform watch state is Trakt, which both Plex and Jellyfin can sync to. Trakt’s free tier exists but VIP features (history exports, more extensive tracking) run about $3/month. More importantly, Trakt is a cloud service — your watch history lives on their servers. WatchState keeps everything local.

Self-hosted WatchState:

  • Software: $0 (MIT)
  • Server resources: minimal. A single Docker container with a small SQLite database. This can comfortably run on a $5/month VPS or alongside an existing home server without measurable impact on resources.
  • Your time: 20–60 minutes for a clean install, depending on experience.

Savings math:

If you’re currently paying $3/month for Trakt VIP to get cross-server sync, WatchState replaces that at $0 in recurring costs. Over a year: $36 back in your pocket, plus you get to stop trusting a cloud service with your viewing history. That’s not the Zapier-to-Activepieces scale of savings, but it’s the right tool for the right problem.


Deployment reality check

The install path is Docker Compose, and it’s genuinely simple for anyone who has run a container before:

services:
    watchstate:
        image: ghcr.io/arabcoders/watchstate:latest
        user: "${UID:-1000}:${UID:-1000}"
        container_name: watchstate
        restart: unless-stopped
        ports:
            - "8080:8080"
        volumes:
            - ./data:/config:rw

One docker compose up -d later and the WebUI is available at port 8080 [README].

The catch is the user permission requirement. WatchState runs rootless — the container user must match the owner of the data directory. This trips up Unraid users in particular: the README explicitly warns to set --user 99:100 for Unraid before first start, and to run chown -R 99:100 /mnt/user/appdata/watchstate if you missed it [README]. If the container can’t write to its config directory it crashes silently, which looks like a broken install when it’s actually a permissions problem.

The Unraid Community Applications path avoids this — the plugin ships preconfigured [README][2].

What you need to add each backend:

  • Server URL (e.g., http://192.168.1.10:8096 for Jellyfin)
  • API key or credentials
  • For Plex: you can authenticate via the “Login with Plex” button — no manual token hunting required [1]

What can go wrong:

The most significant operational risk right now is the Plex external user situation. As of March 2026, Plex changed their API in a way that prevents WatchState from generating access tokens for invited/external users. Home users and managed users on your own Plex server still work fine, but if you run a shared Plex server with friends who have their own accounts, those accounts are no longer syncable through WatchState [README]. This is a Plex-side API decision, not a WatchState bug, but the effect is the same: a feature that previously worked no longer does and there’s no known workaround.

The second risk is project size. Seven contributors and one primary maintainer means the bus factor is high. The project has been actively maintained (71 releases, a meaningful CHANGELOG), but anyone betting their media setup on it should have a plan for the scenario where updates stop.

For a technical user on a home server: 15–30 minutes to a working instance. For Unraid users: under 10 minutes via Community Applications. For someone who has never run Docker: budget a few hours and follow the README carefully.


Pros and cons

Pros

  • Does exactly one thing, well. No feature bloat. The scope is sync play state between Plex, Jellyfin, and Emby. It delivers on that with webhook support, scheduled sync, bidirectional or one-way modes, and progress tracking — not just binary watched/unwatched [README][1].
  • Fully local — no cloud dependency. Your watch history stays on your server. No Trakt account, no third-party service that can be shutdown, acquired, or rate-limited [1].
  • MIT licensed. Clean license, no “fair code” restrictions [README].
  • Rootless Docker container. Better security posture than tools that require root [README].
  • Handles cross-machine setups. Doesn’t need to run on the same server as your media backends — just needs network access to their APIs [1].
  • Plex login convenience. No manual token extraction for home/managed Plex users — OAuth login built in [1].
  • Unraid first-class citizen. Preconfigured in Community Applications, has a YouTube install tutorial [README][2].
  • Parity and validation tools. Not just sync — it can tell you when backends disagree, when media is unmatched, and when file references are stale [README].

Cons

  • External Plex users are permanently broken. The March 2026 Plex API change removes support for invited/external users. If you run a multi-household shared Plex setup, this is a deal-breaker [README].
  • Narrow use case. If you run one media server — just Plex, or just Jellyfin — WatchState does nothing for you. You need to be running two or actively migrating between them for it to matter.
  • Small maintainer base. One primary developer, 7 contributors total. 1,333 stars is modest. Community support exists but this isn’t an enterprise-backed project [README].
  • PHP stack. Not relevant for end users, but relevant if you want to contribute or fork — PHP isn’t the most attractive contribution surface for most homelabbers in 2026.
  • No mobile app or notification system. WatchState is a background sync daemon with a WebUI. There’s no push notification when sync fails, no alerting integration — you check the logs or set up your own monitoring.
  • Rootless permission gotcha. The user/UID setup trips up a meaningful portion of new installs based on how prominently it’s called out in both the README and third-party guides [README][2].

Who should use this / who shouldn’t

Use WatchState if:

  • You run both Plex and Jellyfin (or Emby) and want consistent watch history across them.
  • You’re evaluating whether to migrate from Plex to Jellyfin and don’t want to start a fake A/B test where your watch history doesn’t match.
  • You share a media server with household members who use different apps (some prefer Plex, some prefer Jellyfin).
  • You currently use Trakt as a sync layer and want to remove that cloud dependency.
  • You run Unraid and want the simplest possible path to get this working.

Skip it if:

  • You only run one media server. There’s nothing to sync.
  • Your Plex setup is primarily for external/invited users — the March 2026 API change makes those accounts unsyncable [README].
  • You need robust alerting and monitoring around sync failures. WatchState doesn’t ship that.
  • You’re not comfortable with Docker and don’t have a technical person who can set it up once.

Alternatives worth considering

  • Trakt.tv — the cloud-based option. Both Plex and Jellyfin have official Trakt plugins. Watch history syncs through Trakt’s servers. Free tier exists; VIP is ~$3/month. If you’re fine with cloud and want the social/tracking features (stats, lists, recommendations), Trakt is the mature choice. WatchState is the answer if you want Trakt’s cross-server sync without the cloud dependency.
  • PlexAniSync — if your library is primarily anime and you care about AniList/MyAnimeList sync specifically, this is more targeted.
  • Jellyfin-to-Plex manual migration scripts — community scripts exist for one-time migration of watched state. Right for “I’m switching permanently and just need to migrate once,” wrong for ongoing bidirectional sync.
  • Kometa (formerly Plex Meta Manager) — handles metadata overlays and collection management, not play state. Different tool entirely, occasionally confused with WatchState in search results.
  • Do nothing / restart fresh — viable if you’re fully committed to migrating and don’t care about preserving history for rarely-rewatched content.

The practical shortlist for anyone running both Plex and Jellyfin: WatchState if you want fully local and self-hosted; Trakt if you want a proven cloud-backed option with social features.


Bottom line

WatchState solves a problem that sounds minor until it’s your problem: your watch history fragmented across two media servers that don’t know about each other. The solution is a lightweight Docker container, an MIT license, and no cloud dependency. The setup is straightforward for anyone comfortable with Docker, and the Unraid path is close to zero-friction.

The project earns its use case clearly. What it doesn’t earn is a broader recommendation — it’s a sharp tool for a specific job, not a platform. The Plex external user breakage in March 2026 is a real limitation that removes it from consideration for multi-household shared servers, and the single-maintainer model means long-term reliability depends on one person continuing to care about the project. For a home lab running Plex alongside Jellyfin, those caveats don’t matter. For anything more complex, test carefully before committing.


Sources

  1. Dhruv Bhutani, XDA Developers“This Docker app is a must have if you’re self hosting Plex and Jellyfin” (Nov 15, 2025). https://www.xda-developers.com/this-docker-app-is-a-must-have-if-youre-self-hosting-plex-and-jellyfin/
  2. Spencer Jones, Unraid Newsletter“Unraid April Digest #066” (May 1, 2025). https://newsletter.unraid.net/p/unraid-april-digest-df28

Primary sources:

Features

Integrations & APIs

  • Plugin / Extension System
  • Webhooks

Automation & Workflows

  • Scheduled Tasks / Cron

Data & Storage

  • Backup & Restore

Analytics & Reporting

  • Reports