DiyMediaServer
Featured image of post Trakt Alternative: I Moved My Watch History to WeTrakr

Trakt Alternative: I Moved My Watch History to WeTrakr

Looking for a Trakt alternative? A Jellyfin plugin defect killed my sync for 54 days. How I diagnosed it, exported my history and moved it all to WeTrakr.

Search “trakt alternative” and you get quite the list that looks something like this: nine services, a feature grid, one polite paragraph each, and a conclusion that says it depends on your needs. Not one of those posts was written by somebody whose scrobbler had quietly died. Mine was dead for fifty-four days and I never noticed, because a broken scrobbler and a scrobbler with nothing to do look exactly the same.

I found it by accident. Five episodes into Blue Murder Hotel I happened to open Trakt, and none of them were there. Not delayed. Not out of order. Just missing. When I went back through the logs afterwards, the plugin had been failing in three separate ways for months and had never once told me.

Here’s my story of why Trakt died and why I switched services. For me, three separate things were broken, and only one of them was Trakt’s fault.

💭
TL;DR: A Jellyfin plugin defect killed my Trakt sync for 54 consecutive days without surfacing one error in the UI, and Trakt’s free-tier 100-item collection cap made repairing it pointless against a 2,245-movie library. I exported my history from Trakt’s own settings page and moved to WeTrakr.

I ran all of this on Jellyfin 12.1 in an unprivileged LXC (192.168.1.4, VMID 102) on a Proxmox 9.2.4 node running kernel 7.0.14-6-pve, with the Trakt plugin at 30.0.0.0 and WeTrakr 0.1.24.1 in its place since the beginning of September. Every log snippet below came out of that container.

A scrobbler is the one service that won’t tell you it’s broken

Think about what everything else in your stack does when it fails. Sonarr goes red. SABnzbd stacks up a queue you can see. Jellyfin refuses to play a file and throws an ffmpeg error at your TV. These failures have an indicator.

A scrobbler has no noticeable indicator. It’s a background scheduled task pushing data at an API, and the only place its output lives is a website you rarely check. When it stops, your library still plays, your watched flags in Jellyfin still tick over correctly, and the dashboard stays green. Nothing anywhere says “your watch history stopped pushing back in June.” You find out the way I did, or you find out in two years when you go looking for something you know you watched.

Three faults, and only one of them belonged to Trakt

They stacked up rather than replacing each other, which is why the symptom kept changing and why I couldn’t figure it out initially.

HTTP 420: the account limit you cannot configure away

The oldest fault was policy. My movie library is 2,245 titles. Trakt’s free tier caps a collection at 100 items. So every collection sync ran into this, on every run, for as far back as my logs reach:

Movies to add to collection: 2244
[ERR] Exception handled in PostToTrakt
System.Net.Http.HttpRequestException: Response status code does not indicate success: 420 (<none>).
   at Trakt.Api.TraktApi.PostToTrakt[T](...)
   at Trakt.Api.TraktApi.SendLibraryUpdateAsync(ICollection`1 movies, ...)
   at Trakt.ScheduledTasks.SyncLibraryTask.SendMovieCollectionUpdates(...)

420 is not a rate limit and it isn’t a bug. Trakt’s API reference defines it as Account Limit Exceeded, returned when you go past your collection, watchlist, list, note or favorite cap. Free accounts get 100 collection items. I was posting 2,244 at a wall, roughly ten times a day, and the API was politely refusing every single one past the first 100.

Two things confirmed the cap independently of the status code, and they’re worth knowing because you can check them yourself. The import task logged Trakt.tv watched shows for user jellyfin: 100 and watched movies for user jellyfin: 100 on every run. Exactly 100. Never 99, never 101. And the failure was scoped precisely to the collection endpoint; scrobbles and watched-history calls came back with entirely different codes. When a number in your logs is suspiciously round, it isn’t your data. It’s a limit.

There is no configuration that fixes this. The options are Trakt VIP or leaving collection sync switched off permanently.

The NullReferenceException that took the whole task down

The fault that actually killed sync belonged to the plugin rather than to Trakt, and it has a precise timestamp. The 08:05 run on 2026-06-15 finished normally. The 10:06 run did this:

[ERR] Error executing Scheduled Task
System.NullReferenceException: Object reference not set to an instance of an object.
   at Trakt.Extensions.MetadataIsDifferent(TraktEpisodeCollected collectedEpisode, Episode episode)
   at Trakt.ScheduledTasks.SyncLibraryTask.SyncShows(...)
   at Trakt.ScheduledTasks.SyncLibraryTask.SyncUserLibrary(...)
   at Trakt.ScheduledTasks.SyncLibraryTask.ExecuteAsync(...)

Read the bottom of that stack, because it tells us what we need to know. The exception escapes SyncShows and lands in ExecuteAsync, the task runner’s entry point. It didn’t kill the show stage. It killed the task.

Export library to trakt.tv reported Failed on every run after that. About eleven runs a day, for 54 consecutive days, from 2026-06-15 to 2026-08-08. Nothing reached Trakt in that window. And here’s the tell I missed for two months: each failed run gave up in 10 to 13 seconds, against the 28 seconds a healthy run had taken. The task got dramatically faster and I never noticed, because a scheduled task that finishes quickly and marks itself failed in a log nobody reads is basically invisible.

Known plugin failure modes center on this kind of thing: null references in metadata comparison, stale Trakt.xml configs surviving reinstalls, and token-expiry handling that needed the config file deleted before it would recover. None of that is Trakt refusing your data. That’s a community integration with a defect.

HTTP 422: the plays that bounced one at a time

Underneath both of those, individual scrobbles were being rejected on a completely separate code path:

[ERR] Exception occurred while sending a playback status update to trakt.tv for user jellyfin.
System.Net.Http.HttpRequestException: Response status code does not indicate success: 422 (Unprocessable Entity).
   at Trakt.Api.TraktApi.SendEpisodeStatusUpdateAsync(...)
   at Trakt.ServerMediator.KernelPlaybackProgress / KernelPlaybackStopped

Two to fourteen a day, throughout. 422 means the request was syntactically valid and semantically wrong: an ID Trakt couldn’t resolve, a payload it couldn’t match to a real episode. Different endpoint, different cause, unaffected by anything I did to collection sync. Three faults, three code paths, one quiet but broken outcome.

Trakt free account limits: the honest chronology

Blaming 2026 changes for all of it is easy and wrong. The 2026 changes are what closed the door.

In January 2025, Trakt cut its free tier to a freemium model: 2 personal lists at 100 items each, 100 items in the watchlist, 100 items in the collection. That’s the change that made a homelab-sized library structurally incompatible with free collection sync, and it landed more than a year before anything else on this list.

February 2026 restructured the limits again and raised some of them (watchlist and personal list items went from 100 to 250). Better, and nowhere near what most of us have in our libraries.

July 2026 limited free accounts to one connected Community App at a time. Jellyfin or Stremio or Kodi or Syncler. Pick one. The website and official apps are exempt, which turns out to matter when you go to export.

August 2026 pushed API key access behind VIP.

So the honest sequence is: the caps made a full repair impossible in early 2025, the plugin defect made sync stop entirely in June 2026, and the 2026 policy changes removed the workarounds I’d have reached for. Trakt is entitled to charge for its service. The plugin bug is not Trakt’s to fix. Both of those can be true while the outcome is still that I left.

Jellyfin Trakt plugin not syncing: how to tell right now

Nothing surfaces in the Jellyfin UI, so you’re going to the logs. Two places, in this order.

Dashboard → Scheduled Tasks. Find Export library to trakt.tv. Look at three things: is it enabled, when did it last run, and what was the result. A red Failed here is the whole diagnosis in a glance, and it’s been sitting on that screen the entire time.

The server log. Grep it for the plugin’s own lines:

# systemd install (LXC or bare metal)
journalctl -u jellyfin --no-pager -o short-iso | grep -i trakt | tail -50

# Docker install
docker logs jellyfin 2>&1 | grep -i trakt | tail -50

# Jellyfin's own daily file, whichever install you run
grep -i trakt /var/log/jellyfin/*$(date +%Y%m%d)*.log | tail -50

Then count the failures by day, which is what turns “it seems flaky” into a date you can point at:

journalctl -u jellyfin --no-pager -o short-iso \
  | grep -i "does not indicate success" \
  | awk '{print substr($1,1,10)}' | uniq -c

Read the result like this. A 420 is your account limit and no amount of reinstalling fixes it. A 422 is a specific item Trakt won’t accept, and it’ll keep happening on that item. A stack trace with NullReferenceException or a JSON token complaint is the plugin, and your only levers are a version change or a config reset. A task that never runs at all was never enabled.

That config reset needs a warning attached, because it’s the one piece of advice in this post that could cost you. Resetting means deleting Trakt.xml and re-authorizing from scratch, and that file holds every plugin setting you ever ticked along with your OAuth tokens. Copy it first, with Jellyfin stopped:

# systemd install
systemctl stop jellyfin
cp /var/lib/jellyfin/plugins/configurations/Trakt.xml \
   /var/lib/jellyfin/plugins/configurations/Trakt.xml.bak
rm /var/lib/jellyfin/plugins/configurations/Trakt.xml
systemctl start jellyfin

# Docker install: same file, inside your mounted config volume
# /config/plugins/configurations/Trakt.xml

Then re-authorize the device in the plugin UI and run the export task by hand. And know what the backup is worth: the .bak still carries live tokens, so it belongs somewhere you’d be happy putting a password. If you’re walking away from Trakt entirely, revoking the authorization on Trakt’s site is the only thing that certainly kills it. Deleting the file removes your copy, not their permission.

⚠️
Warning: Before you trust any of this, confirm your logs actually persist. My Jellyfin container runs journald with Storage=volatile, which I set myself in a hardening pass, so the on-disk journal froze at a boot boundary and everything after it lived in tmpfs until the next reboot ate it. Jellyfin’s own daily files keep ten days. I reconstructed this entire history with about a week of data. Check grep ^Storage /etc/systemd/journald.conf on the box you care about, while nothing is broken.

My one troubleshooting session, and what it bought me

I gave it one session before giving up. Reinstalled the plugin. Re-authorized the device against Trakt four times. Ran the tasks by hand, repeatedly. None of it changed anything, because a NullReferenceException in metadata comparison doesn’t care how fresh your OAuth token is.

Then I unchecked Sync Jellyfin library to Trakt Collection, which sets SynchronizeCollections to false. The 420s stopped instantly and the export task completed for the first time in 54 days. Great, right?

Not really. MetadataIsDifferent only runs while comparing collected episodes, so disabling collection sync skipped the code path rather than fixing the defect. I inferred a fix at the time. I was wrong, and the later daily logs proved it by recording ten more occurrences of the same exception a fortnight afterwards. Skipping a code path is not repairing it. Write that on the inside of your skull.

And with the task finally completing, a fourth problem came into view:

[ERR] Episode not found on trakt.tv: {"Season": 0, "Number": 0, "Title": null,
  "Ids": {"Tvdb": "402910", "Tvrage": null, "Imdb": "tt14218830", "Tmdb": 125935,
          "Trakt": null, "Slug": null, "$type": "TraktEpisodeId"}, "$type": "TraktEpisode"}

345 of those per run. Every one carrying season 0, episode 0, while also carrying perfectly good TVDB, IMDb and TMDB IDs. Trakt cannot match an episode from season 0, episode 0 and a null title, so it rejected all of them. That pattern started the day the plugin went to version 29.0.0.0 and it never went away. I never solved it. Three weeks later I pulled the plugin.

Repair, pay, or leave

Three real options. Pick honestly.

Pay for VIP. Removes the caps, removes the one-app rule, keeps the ecosystem, keeps every scrobbler integration that already works. If Trakt’s social and discovery features are why you’re there, this is the correct answer. I’m not quoting you a price, because Trakt has restructured its tiers twice since January 2025 and any number I typed here would be stale before you read it. Go and read the current VIP page, then decide against that number rather than against my memory of it.

Run Trakt minimally. Keep watch history and ratings, leave collection sync off forever, and treat your Jellyfin library as the only record of what you own. This works and plenty of people do it. It also means accepting that the plugin can break silently again tomorrow.

Move the scoreboard. Export your data, import it somewhere else, and let Trakt become a historical archive.

I went with the third. Trakt is a decent service run by people who have to pay for it. I had a 2,245-movie, 17,040-episode library, a free account that could never represent it, and a plugin defect nobody was going to fix on my schedule.

Dell OptiPlex 7070 Micro
Dell OptiPlex 7070 Micro. A 1-liter business mini PC with 8th/9th-gen Intel Core CPUs that’s quiet, efficient, and upgradeable (NVMe storage, dual SODIMMs) while disappearing behind a monitor. Great as a tidy Proxmox/Plex node or an everyday office box, with solid build quality and enough ports for dual-display setups.
Amazon Price: Loading...
Availability: Checking...
Contains affiliate links. I may earn a commission at no cost to you.

Export your Trakt data first. Always first.

Do this before you create an account anywhere else. Before you touch a plugin. Before you make any decision at all, because the export is free, it’s reversible, and it’s the one artifact that survives every choice you make afterwards.

Go to your Trakt account settings on the website and use the export options. You’ll get your watched history for movies and episodes, your ratings, your watchlist and your lists. It arrives as a JSON zip with every play in it, including timestamps and rewatch entries.

Why the settings export instead of letting your new tracker connect to Trakt with OAuth? Because since July 2026 a free account gets one connected Community App, and an OAuth import spends it. Authorize a new service and you may knock Jellyfin off the connection you’re still using. The website is exempt from that rule, so the export path costs you nothing. You also end up with a frozen snapshot you can feed into a second or third service later, which matters more than it sounds like, because you’re going to be tempted to try two of them.

Sanity-check the zip before you go further. Count the watched movies and episodes. Compare that against what your media server thinks you’ve watched. If the export is short, your plugin was already dropping data, and that’s the moment to find out rather than after you’ve decided the import lost it.

WD 14TB Elements Desktop External Hard Drive
WD 14TB Elements Desktop External Hard Drive. A desktop external drive that packs 14 TB into a USB 3.0 enclosure, making it one of the cheapest ways to add bulk storage to a homelab or grab a large drive for shucking into a NAS. WD’s CMR recording on these capacities means solid sustained write speeds, and the wide compatibility with Windows, Mac, and Linux makes it an instant-plug backup target.
Amazon Price: Loading...
Availability: Checking...
Contains affiliate links. I may earn a commission at no cost to you.

Importing into WeTrakr and checking nothing fell out

WeTrakr is a hosted, multi-source tracking hub for movies, TV and anime, with a one-step import that covers watchlist, ratings, lists and history from Trakt, Letterboxd, IMDb, TMDB, Simkl and TV Time. It’s a plugin on the Jellyfin side and a webhook token on disk.

The order that works is history, then ratings, then watchlist and lists. History is the big one and the one you should care about, so let it settle before you layer anything on top of it. Concretely:

  1. Create the WeTrakr account at https://wetrakr.com and confirm it before you touch your export.
  2. Open the import screen in your account settings. It’s one screen for every supported source, Trakt included.
  3. Choose Trakt as the source and feed it your export. Trakt hands you a zip of JSON files, so if the picker will only take a single .json, unzip it first and upload the history, ratings and watchlist files one at a time. If the only route on offer is an OAuth connection to Trakt, stop and reread the previous section, because that connection spends your one free Community App slot and can knock Jellyfin loose.
  4. Run the history import on its own and let it finish. It’s the largest job and the one every count you check later is measured against.
  5. Then ratings, then watchlist and lists. Small, quick, and cheap to redo if something reads wrong.
  6. Install the WeTrakr plugin in Jellyfin and link it to the account. It writes a per-link webhook token into its own plugin config, and from then on it posts plays, watched states and ratings as they happen.

Verification is where people get sloppy, and it’s the only part of a migration you can’t redo easily once you’ve deleted the source. Compare total watched movies and total watched episodes between your Trakt export and the new service’s stats page. Then pick a show you’ve rewatched, open it, and check the per-episode play counts. Rewatch counts are the first thing a lazy importer flattens, and they’re the last thing you’ll think to check.

Then go find the ugly cases on purpose. A long-running show with a partial season. Anything with specials. Anime, if you have it, because anime numbering is where every tracker’s ID mapping goes to die. In my import the only titles that didn’t come across were obscure shows I watched years ago and no longer care about. Your mileage genuinely varies with how weird your library is.

On the Jellyfin side, the plugin has been running clean since 2026-08-28. It scrobbles playing, watched and ratings, its scrobble manager has started without complaint on every restart since, and there is not one WeTrakr error line in any of my logs. That’s a low bar. It is also more than the thing it replaced was clearing.

What WeTrakr does not replace yet

Its public API is still in development, and it isn’t at Trakt’s level of maturity or third-party integration. If you have tooling that speaks the Trakt API (a dashboard, a script, a recommendation engine you built), that tooling does not move with you. Plan for periodic imports or WeTrakr’s own clients instead.

Now the part I’d be dodging if I left it at that. Nothing about WeTrakr runs in my lab. I swapped one hosted scoreboard for another, and every failure mode I’ve described here is available to it too. It can add a paid tier. It can cap free accounts at some number my library sails past. It can go quiet and take my history with it, and I’d have exactly the leverage I had in June, which was none. What I bought was a service whose current limits fit my library and a maintainer who hasn’t yet had a reason to change them. That is a smaller purchase than it feels like on the day you migrate.

The insurance policy is the scheduled export, pointed at whatever you’re using now, including this one. Vendor loyalty buys you nothing.

So treat WeTrakr as the scoreboard for history, ratings and lists, and keep your media server’s internal watched state as the day-to-day source of truth. That’s the right architecture anyway, and it’s the one I should have been running all along.

Beelink SER5 (Ryzen 5 5500U)
Beelink SER5 (Ryzen 5 5500U). A palm-sized mini PC with a 6-core/12-thread CPU that feels snappy for everyday work and homelab duties, handling Docker stacks, light VMs, and Plex without guzzling power. With NVMe plus a 2.5″ bay, Wi-Fi 6, and multi-display output, it’s a quiet, tidy upgrade for desk or media setups.
Amazon Price: Loading...
Availability: Checking...
Contains affiliate links. I may earn a commission at no cost to you.

The Trakt alternatives I didn’t pick

Simkl is the closest like-for-like swap. Built-in Trakt importer, first-class automatic scrobbling across Plex, Kodi, Emby, Netflix and Crunchyroll, strong TV and anime coverage. If automatic scrobbling from many sources is your top priority, go look at it before you look at anything else. I installed the plugin the same afternoon I left Trakt and it didn’t last the hour. Going by the timestamps on the config it left behind rather than on my memory, WeTrakr had replaced it about forty-five minutes later, which tells you how much of an evaluation it got. Reports of server strain during migration waves didn’t help, and I’d stopped wanting a tracker whose main selling point was more aggressive automation.

Serializd is television only. Good-looking, genuinely social, imports from Trakt and TV Time. My movie history is half my history, so it was never going to work here. If your stack is TV-first and you want people to talk to, it’s a real answer.

Ryot and Movary are the self-hosted options, and they’re the ones that fit my instincts best. No caps, no tier, no policy change landing in your inbox. I just wanted something easy and not another thing to maintain.

Raspberry Pi 5 (8GB) kit
Raspberry Pi 5 (8GB) kit. A Pi 5 8GB board bundled with a case, an active cooler, and a screwdriver, which covers the cooling and enclosure you’d otherwise source separately. The board is the biggest generational leap in Pi history. A quad-core Cortex-A76 at 2.4 GHz gives it roughly twice the CPU performance of a Pi 4, with PCIe 2.0 via the new FFC connector for NVMe storage, a real power button, and a dedicated Raspberry Pi RP1 I/O chip. It still sips power and fits in your hand, but now handles heavier Docker workloads, Pi-hole plus Home Assistant simultaneously, and even light Jellyfin duties.
Amazon Price: Loading...
Availability: Checking...
Contains affiliate links. I may earn a commission at no cost to you.

Frequently Asked Questions

➤ Why did my Jellyfin Trakt plugin stop syncing with no errors?
Because Jellyfin surfaces nothing about plugin scheduled tasks in the main UI. Check Dashboard → Scheduled Tasks for Export library to trakt.tv and read its result, then grep the server log for trakt. In my case the task had reported Failed on every run for 54 days.
➤ What does Trakt HTTP 420 Account Limit Exceeded actually mean?
It means you’ve exceeded a free-account ceiling, most often the 100-item collection cap. This is an account ceiling rather than throttling. Trakt’s API returns 420 for collection, watchlist, list, note and favorite limits. No plugin setting fixes it. Your choices are VIP or leaving collection sync disabled.
➤ Can a large media library work with a free Trakt account?
For watch history and ratings, yes. For collection sync, no. A 2,245-movie library against a 100-item collection cap fails every push. Disable Sync Jellyfin library to Trakt Collection, keep scrobbling, and treat your media server as the record of what you own.
➤ Should I use Trakt's export or let the new service import over OAuth?
Use the export. Since July 2026 a free Trakt account allows one connected Community App, and an OAuth import consumes that slot, potentially disconnecting Jellyfin. The website export is exempt from that rule, costs nothing, and gives you a frozen snapshot you can reuse across several services.
➤ Does WeTrakr replace Trakt for Jellyfin scrobbling yet?
For scrobbling, yes. The Jellyfin plugin has posted plays, watched states and ratings cleanly for me since 2026-08-28. For integrations, not yet. Its public API is still in development, so anything you built against Trakt’s API will not move across with your history.

What I actually took away from this

I own every layer of my media stack. The hypervisor, the containers, the storage, the network, the reverse proxy, the media server, the automation that fills it. All of it is mine, all of it is monitored, and I get a push notification when a login fails.

Except the one keeping score. That lived somewhere else, on somebody else’s server, behind a plugin somebody else maintains, and I couldn’t tell from my living room that it had been broken since June.

So do the two things that would have saved me.

Monitor the scrobbler’s scheduled task result the same way you’d monitor a backup job, because an unwatched background task is a backup you never test. Jellyfin will hand you that result over its API, which beats relying on yourself to open a dashboard you never open:

curl -s -H 'Authorization: MediaBrowser Token="YOUR_API_KEY"' \
  http://192.168.1.4:8096/ScheduledTasks \
  | jq -r '.[] | select(.Name | test("trakt"; "i"))
           | "\(.Name): \(.LastExecutionResult.Status)"'

Anything coming back other than Completed is your cue. Wrap it in a cron job that pushes a notification through something like ntfy.

Then export your history on a schedule, from whatever tracker you land on, whether that’s Trakt, WeTrakr or some other Trakt alternative you pick next year. Trakt’s API sits behind VIP now, so on a free account that schedule is a recurring calendar reminder rather than a cron job. Set it anyway. Not when you’re migrating. Now, while nothing is broken.