Sync Plex watch history to Trakt with PlexTraktSync in Docker
On this page
- What PlexTraktSync does
- Prerequisites
- Step 1: Create a Trakt API app
- Step 2: Create the Docker Compose file
- Step 3: Authenticate with Trakt
- Step 4: Configure Plex (the Google OAuth workaround)
- Getting your Plex token
- Getting your server details
- Writing the config files
- Step 5: Docker networking: use host.docker.internal
- Step 6: Run the initial sync and start watching
- Verifying it works
- Wrapping this up
- References
Want your Plex watch history in Trakt without paying for Plex Pass (I’m already paying for that, thanks, but you don’t have to) or Trakt VIP? PlexTraktSync does exactly that, and it runs just fine in Docker.
I started setting this up while building an AI-powered recommendation service (in the form of a locally hosted MCP server) for my Plex library. Trakt integration was the obvious next step: more watch history, ratings, and metadata usually means better recommendations.
The setup looks straightforward until Plex login starts fighting you, especially when your account uses Google and the whole thing runs in containers. I ran into both, so this is the version that actually worked.
What PlexTraktSync does
PlexTraktSync does a few useful things:
- Syncs watched status from Plex to Trakt
- Syncs ratings and collections
- Pushes Plex media info into your Trakt collection
- Syncs watchlists and liked Trakt lists back to Plex
- Can run in
watchmode to scrobble (media-tracker jargon) in real time over a websocket
Prerequisites
- A Plex server (duh)
- A Trakt account (free)
- Docker and Docker Compose (a pretty basic requirement, if you follow my blogs)
Step 1: Create a Trakt API app
- Go to https://trakt.tv/oauth/applications/new
- Give it a name, for example
PlexTraktSync - Set the redirect URL to
urn:ietf:wg:oauth:2.0:oob - Leave Javascript origins and Permissions blank
- Save the app, then copy the Client ID and Client Secret
Step 2: Create the Docker Compose file
Create a directory for PlexTraktSync and add a docker-compose.yml file:
version: "2"
services:
plextraktsync:
image: ghcr.io/taxel/plextraktsync
container_name: plextraktsync
command: watch
restart: unless-stopped
volumes:
- ./config:/app/config
environment:
- PUID=1000
- PGID=1000
- TZ=Europe/Amsterdam
The watch command keeps the container running, listens for Plex events, and scrobbles them to Trakt in real time. restart: unless-stopped makes sure it comes back after a reboot unless you stopped it yourself.
This setup keeps your Trakt watch state in sync with Plex, but it does not import your existing history yet. That part comes later. Let’s first talk about the setup.
Step 3: Authenticate with Trakt
Trakt uses device-based authentication, so this step needs to be interactive, meaning you have to enter some credentials:
docker compose run -it --rm plextraktsync trakt-login
It will ask for the Client ID and Client Secret from step 1, and will then provide you with an authentication URL and device code to confirm in your browser.
Use trakt-login, not login, unless you want to get dumped into the Plex login flow for no reason. I’m going through this in a specific order for a reason, so stay with me here.
Step 4: Configure Plex (the Google OAuth workaround)
This is where things got a bit annoying.
The interactive login command expects a Plex username and password. That breaks when your Plex account uses Google, Apple, or another OAuth provider. Instead of logging in, it just loops with a 401 User could not be authenticated error, making you doubt your email, username and password. Well, I say NO MORE!
The workaround is simple enough: grab your Plex token manually and write the config files yourself. Which, in my opinion, should be the default authentication anyway.
Getting your Plex token
- Open Plex Web and sign in with Google
- Open any media item
- Click the … menu, then Get Info, then View XML
- Check the URL bar for
X-Plex-Token=xxxxxxxxxxxx(it will be at the end of the URL)
Another option: open browser dev tools (F12) on app.plex.tv, go to the Network tab, and look for X-Plex-Token in a request header or URL parameter.
Getting your server details
Once you have the token, query the Plex API to get your server URL, machine identifier, and related details:
curl -s \
-H "X-Plex-Token: YOUR_TOKEN" \
-H "X-Plex-Client-Identifier: plextraktsync" \
-H "Accept: application/json" \
"https://plex.tv/api/v2/resources?includeHttps=1" | python -m json.tool
This returns all available servers with their connection URLs, machine identifiers, and access tokens.
Writing the config files
Create config/servers.yml with your server details:
servers:
default:
token: YOUR_PLEX_TOKEN
urls:
- http://host.docker.internal:32400
id: your-server-machine-identifier
config: null
If you’re wondering what host.docker.internal is doing there, good, that matters in the next step.
Update the config/.env with the retrieved data:
# This is .env file for PlexTraktSync
PLEX_USERNAME=your_plex_username
TRAKT_USERNAME=your_trakt_username
PLEX_SERVER=default
PLEX_OWNER_TOKEN=
PLEX_ACCOUNT_TOKEN=YOUR_PLEX_TOKEN
Step 5: Docker networking: use host.docker.internal
This one can take a while to figure out. Luckily, or not, I had already run into the exact same issue earlier when I switched some of my Docker containers from Dockerfiles to Compose, so I recognized the problem pretty quickly.
My Plex server was running on the same machine, but the container still couldn’t reach it through the LAN IP (192.168.1.x). It also couldn’t resolve the plex.direct hostnames Plex uses for its SSL setup, those lovely things like 192-168-1-50.xxxx.plex.direct. And no, those - signs are not a mistake.
The fix was to use host.docker.internal instead of the host IP. Docker Desktop on Windows and macOS maps that hostname back to the host machine automatically. Depending on your machine and Docker networking setup, the direct IP might work, but host.docker.internal is the safer bet.
urls:
- http://host.docker.internal:32400
Got a public Plex URL as well? Add it as a fallback:
urls:
- http://host.docker.internal:32400
- https://your-public-plex-url:port
Step 6: Run the initial sync and start watching
Remember when I mentioned the container would keep your watch status synced while you use Plex? This is the missing step to get your historic data into Trakt.
First, run a one-time sync to push your existing Plex history into Trakt:
docker compose run --rm plextraktsync sync
If you’d rather sync a specific library instead of everything, use the --library flag:
docker compose run --rm plextraktsync sync --library "TV Shows"
You can also target a specific show or movie:
docker compose run --rm plextraktsync sync --show "Breaking Bad"
docker compose run --rm plextraktsync sync --movie "Inception"
Once the sync is done, the container exits and cleans itself up. The perfect crime. On a big library, this can take a while, think hours. Once that’s done, start the long-running watcher we created before:
docker compose up -d
From there, the watch command sends new plays, ratings, and collection changes to Trakt as they happen. PlexTraktSync calls this “scrobbling”, which is just tracker jargon for automatically sending what you’re watching to a service like Trakt.
Verifying it works
Check the container logs:
docker logs plextraktsync --tail 20
You should see something like this:
INFO Listening for events!
INFO Server connected: Plex (version)
INFO Websocket connected
That means PlexTraktSync is connected to your Plex server over websocket and is actively listening for playback events. If you see connection or timeout issues, check that the configured IP and service URLs are correct.
Now play something in Plex and check Trakt. If everything is working, it should show up there without you having to poke it any further.
Wrapping this up
Once that works, you’re done. PlexTraktSync keeps Plex and Trakt in sync. The container starts automatically after a reboot, and you no longer have to babysit the whole thing manually, which is exactly how this should have worked from the start.