PlexTraktSync: two-way Trakt and Plex sync without a PlexPass
A python script that syncs the movies, shows and ratings between trakt and Plex (without needing a PlexPass or Trakt VIP subscription)
At a glance
- What is it?
- PlexTraktSync pairs a Plex Media Server library with trakt.tv over plain API keys, moving ratings, watched flags and watchlists in both directions without a Plex premium or Trakt VIP. It is a process you start by hand or on a schedule, and the file it writes your Trakt keys into is the part worth reading twice.
- Who is it for?
- Adopt PlexTraktSync if you run a Plex server and a free trakt.tv account and would rather own one command than fund two subscriptions, since a scheduled `plextraktsync sync` covers a household library that changes weekly. Leave it alone if a Trakt API key must never touch the disk, or if watched dates have to arrive in Plex from Trakt, because that direction is not implemented.
- Can I use it commercially?
- Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
- Is it still maintained?
- Yes. The repository last received commits 1 day ago.
- What is it written in?
- Mainly Python, according to GitHub's language statistics.
Answers come from the project's GitHub data, last synced on October 23, 2026, and from our analysis. They are not legal advice.
Editorial analysis
Two libraries, one command, no paid tier
PlexTraktSync is a Python script that syncs the movies, shows and ratings between trakt.tv and Plex Media Server, and the pitch sits in the second half of that sentence: it asks for a trakt.tv account and nothing else. No Plex premium, no Trakt VIP membership. Media sitting in a Plex library is added to the Trakt collection, ratings are synced, watchlists are synced, and a config file lets you choose which of those pieces actually run.
One direction is a list rather than a match. Liked lists in Trakt are downloaded and every movie in Plex belonging to that list is added, so a curated list you keep in one system reshapes the other without any per-title decision from you. The audience is narrow and obvious: households and small offices with two half-finished libraries, a shared set of users and no intention of paying for the official bridge. The tool was originally created by Taxel and is now maintained by contributors, so treat the command surface as community-owned rather than vendor-backed.
Everything happens on a schedule you control. There is no long-running daemon to supervise and no web interface to learn, which is why the same script suits a NAS, a laptop and a container host equally well.
Trakt's own Plex app trades subscriptions for live data
The alternative is the Plex app provided by Trakt, and the difference is architectural rather than cosmetic. That app lives inside the Plex client and reacts as you watch; PlexTraktSync is a process that finishes and exits, so it can not use live data via webhooks. The same feature bullet that promises no paid tier also states the cost: it needs to be executed manually or via a cronjob.
That choice decides what happens the moment a film ends. Under the app, the watch lands in your history immediately. Under the script, the flag waits for the next scheduled run, and how long that gap runs depends entirely on the cron entry you write yourself. Anyone who watches live television through the Plex clients will feel that lag first, since live viewing is exactly the traffic a batch job serves last.
Odd detail worth noting: `requirements.txt` still pins `websocket-client==1.9.2`, while the feature list rules out webhook-driven live data. Nothing in the available documentation says which part of the code opens a socket. That gap does not change the shape of a sync that runs in batches either way, but it means the socket library is not explained by the user-facing documentation.
Watched flags move, watched dates stay behind
This is the asymmetry that a skim misses, because the project calls itself a two-way sync and most fields behave that way. Ratings are compared in both directions. Watched status is compared in both directions. Watched dates are not reported from Trakt to Plex, and that exclusion is written into the feature list in parentheses rather than buried.
Concretely: mark a title watched in Trakt and Plex learns that it is watched, without learning when. Mark it in Plex and the timestamp does reach Trakt. The documentation does not say what Plex renders when the date is missing, nor whether a later Plex-side play overwrites an earlier flag set from Trakt, and the README does not document rollback for the case where a run writes the wrong direction.
Ratings and watched flags are the safe part of the surface. Watched history is the part to check by hand after the first run on a small library, rather than on a decade of films where a wrong date is hard to notice and harder to fix by hand afterwards.
Identifier matching gets a package of its own
Distribution metadata in `pyproject.toml` points the licence at the `LICENSE` file and tags the build as MIT and Development Status :: 5 - Production/Stable, with the version read dynamically from `plextraktsync.__version__`. The package list is the better map of the design: `plextraktsync.commands` for the CLI, `plextraktsync.config` for paths and options, `plextraktsync.plan` for the change set, and `plextraktsync.plex.guid.provider` nested under `plextraktsync.plex.guid` for identifier resolution. That listing is cut off after `plextraktsync.plugin`, so the full set of subpackages is larger than what any single file view shows.
Giving GUID matching its own provider package is the point of the layout. Two servers that spell the same film differently can only be paired if identifier resolution is a separate, replaceable step rather than logic scattered through the collectors. Two client libraries carry the traffic, `pytrakt==4.4.6` and `plexapi==4.18.2`, with `requests==2.34.2` and `requests-cache==1.3.3` underneath and `platformdirs==4.11.12` deciding where config, logs and cache live on your operating system.
Around the package sit four shell entry points, `entrypoint.sh`, `plextraktsync.sh`, `plex-api.sh` and `trakt-api.sh`, and the two api scripts are not explained anywhere in the available documentation. Dependency pinning is duplicated across `requirements.txt`, `requirements.pipenv.txt` and `Pipfile.lock`, which is normal for a project that supports both pipx and Pipenv installs but does mean a fix can land in one file and not the other.
pipx install, credentials, then a first sync
The shortest documented path is pipx. Install the package, run it once with no arguments, and the tool walks through a credential process before it touches either server; the Unraid section reaches the same point by telling you to enter `plextraktsync` to start that credential process. Then run a sync:
pipx install PlexTraktSync
pipx install PlexTraktSync==0.15.2 --force
plextraktsync syncWatch which number that second line pins. The pipx example shows `0.15.2`, while the container tags shown further down sit in the 0.25 line at `0.25.16` and `0.25`, so the install example in the same document trails the documented image tag by a long way. Copy it as a demonstration of the pinning syntax, not as a version to stay on.
After a run, the info command prints the OS specific config, logs and cache directories that `platformdirs` chose, which matters more than it sounds because a pipx install and a container will not look in the same place for your files. The wider command surface adds unmatched items, an inspection command and a watch mode, with systemd and systemd user setup documented alongside the watch mode for people who want the job running rather than typed.
The compose service mounts one directory and nothing else
The documented compose service is short enough to read in a single pass. It pins an image, mounts one host directory over `/app/config`, and sets `PUID`, `PGID` and a timezone, with `restart: on-failure:2` so a failed sync is retried twice and no more:
version: "2"
services:
plextraktsync:
image: ghcr.io/taxel/plextraktsync
command: sync
container_name: plextraktsync
restart: on-failure:2
volumes:
- ./config:/app/config
environment:
- PUID=1000
- PGID=1000
- TZ=Europe/TallinnHealth monitoring is optional and off by default. Adding the healthcheck entry makes the container run a log scan on an interval and report unhealthy if it finds ERROR or CRITICAL lines from the last hour, which turns a silent failure into a visible state without leaving a long-running process behind:
healthcheck:
test: ["CMD", "plextraktsync", "healthcheck"]
interval: 60s
timeout: 10s
start_period: 30s
retries: 3A one-shot run exits when the sync finishes:
docker compose run --rm plextraktsync syncOne close look at the repository changes the picture. The checked-in `docker-compose.yml` carries an image and a volume and nothing else: no command, no restart policy, no environment block. That file is a bare starting point, and the fuller service definition above is what you copy into your own stack.
API keys land on disk in the clear
This warning sits directly under the project title instead of being buried in a security section, and it deserves to be the first thing read: the PyTrakt API keys are not stored securely, so if you do not want a file containing those on your hard drive you can not use this project. No encryption, no system keyring, no reading from environment variables at runtime. The credential process exists to write a file, which is also why the compose service mounts a host directory at `/app/config`: those keys have to survive the container stopping.
The rest of the dependency picture is ordinary and worth knowing for debugging. Cached HTTP responses come from `requests-cache`, terminal rendering from `rich==15.0.0`, progress bars from `tqdm==4.70.1`, and the interactive credential prompts from `inquirerpy==0.3.4`. A `tests/` directory sits at the top level next to `AGENTS.md` and `CONTRIBUTING.md`, so the project has a place to document changes for other agents and contributors.
Two installation routes are marked unsupported and documented only by user contribution. The Windows path asks you to download the latest `.zip` build from the repository tags page and run `setup.bat` on a machine from Windows 7sp1 through 11. Unraid offers a manual container or the Community Apps plugin, with a cron plugin needed to schedule it. Both work by the same rule: you are reading a contributed note, not a maintained procedure.
The Python range disagrees with the badge and the image
Pre-requisites say the script is known to work with Python 3.10-3.13 versions. The badge at the top of the same file advertises 3.10, 3.11, 3.12, 3.13 and 3.14, `pyproject.toml` carries a classifier for 3.14 alongside `requires-python = ">=3.10"`, and the Dockerfile pins `ARG PYTHON_VERSION=3.13` on Alpine `3.21`. So the packaging metadata claims 3.14 while the published image is built on 3.13 and the prose stops at 3.13.
The lower bound is open ended, so a pipx install on 3.14 will not be refused, yet nothing in the documentation confirms what happens there. What breaks either way is the assumption that a classifier list is a support statement. If you want the documentation and the shipped image to agree, build on 3.13 or run the container and let the image decide.
Upgrading runs through pipx, where `plextraktsync self-update` is only a wrapper around `pipx upgrade PlexTraktSync`, so a container user has no equivalent shortcut and must repull a tag by hand. The last push landed on 2026-09-28 and the repository has no GitHub releases, so the tags page at `https://github.com/Taxel/PlexTraktSync/tags` is where versions are actually found. `main` is the development branch, and reporting bugs against development versions is not supported, which matters when you are bisecting a sync that broke after an upgrade.
Editorial conclusion
Adopt PlexTraktSync if you run a Plex server and a free trakt.tv account and would rather own one command than fund two subscriptions, since a scheduled `plextraktsync sync` covers a household library that changes weekly. Leave it alone if a Trakt API key must never touch the disk, or if watched dates have to arrive in Plex from Trakt, because that direction is not implemented. Before the first full run, check the `ghcr.io/taxel/plextraktsync` tag you pin, then confirm the config directory the info command prints.
Frequently asked questions
Does PlexTraktSync need a Plex Pass or a Trakt VIP subscription?
No. It requires a trakt.tv account, and the feature list states that none of its features need a Plex premium or a Trakt VIP membership. The cost is that it runs manually or via a cronjob and cannot use live data via webhooks.
How do I install PlexTraktSync and run the first sync?
With pipx, `pipx install PlexTraktSync` followed by `plextraktsync sync`. Docker users pull `ghcr.io/taxel/plextraktsync`, mount a host config directory at `/app/config`, and run `docker compose run --rm plextraktsync sync`, which exits when the run finishes.
Which Python versions does PlexTraktSync support?
The pre-requisites call Python 3.10-3.13 known to work, while the badge and the `pyproject.toml` classifiers also list 3.14 and `requires-python = ">=3.10"`. The container image is built on Python 3.13, so 3.13 is the version where documentation and image agree.
Where does PlexTraktSync store my Trakt API keys?
In a file on disk, stored insecurely. The project warns that if you do not want a file containing those keys on your hard drive you can not use it, and the container setup mounts a host directory at `/app/config` so those files survive the container stopping.
Does PlexTraktSync sync watched dates from Trakt to Plex?
No. Watched status is synced but dates are not reported from Trakt to Plex, while ratings and watched flags move in both directions. Watched timestamps travel from Plex to Trakt only.
How do I stop a failing PlexTraktSync container from failing silently?
Add the optional healthcheck block to the compose service. The test command calls `healthcheck` inside the container every 60 seconds and marks it unhealthy if ERROR or CRITICAL lines appear in the log within the last hour.
Official sources
Add this badge to your README
If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.
[](https://hysenlabs.com/projects/taxel-plextraktsync)Was this useful?