Formerly LetterboxdSync. Renamed to Jellyscribe now that it syncs TV shows (via Serializd) alongside films (via Letterboxd), not just Letterboxd. Existing installs update in place automatically, no action needed.
- Website: jellyscribe.dev
- What's new: release notes for every version
- Built with AI: most of this plugin is AI-written, human-reviewed, full transparency in AI.md
Automatically sync your Jellyfin watch history to your Letterboxd diary (films) and Serializd diary (TV). Titles are logged in real-time when you finish watching, with a daily scheduled sync as a safety net.
Uses Letterboxd's current JSON API (/api/v0/production-log-entries) and Serializd's API.
- Real-time sync, titles logged to your diary the moment you finish watching
- Daily catch-up, scheduled task picks up anything missed
- Multi-user, multi-account, each Jellyfin user can link their own Letterboxd account, Serializd account, or both
- TMDb matching, films and episodes matched by TMDb ID, so foreign titles and special characters work
- Duplicate detection, won't log the same title twice on the same day
- Rewatch detection, real-time playback automatically marks rewatches
- Date filtering, limit catch-up syncs to recently watched titles
- Rating sync, both ways, Jellyfin ratings (0-10) map to Letterboxd stars (0.5-5.0) or Serializd's 1-10 scale, and ratings you set on either service seed your Jellyfin user rating back
- Favorites, sync Jellyfin favorites as Letterboxd likes or Serializd likes
- Reviews, write and post reviews to Letterboxd (films) or Serializd (shows or individual episodes) from the plugin dashboard
- Diary import, mark Jellyfin movies or episodes as played if they're already in your Letterboxd or Serializd diary
Full feature parity with the Letterboxd side: real-time sync, ratings, reviews, favorites, diary import, rewatch detection, watchlist sync, and Seerr auto-request/backfill/mirror all work the same way for Serializd as they do for Letterboxd, just scoped to TV episodes instead of films.
- TV sync, finished TV episodes are logged to your Serializd watched list in real time, the TV counterpart to the Letterboxd film sync
- Per-user accounts, each Jellyfin user links their own Serializd account (by email or username), with a Verify login button and passwords encrypted at rest
- Daily catch-up, a "Sync watched TV to Serializd" scheduled task picks up anything real-time missed, plus a Sync TV Now button
- TMDb matching, episodes matched by their series' TMDb id + season/episode number
- Isolated from Letterboxd, films still sync to Letterboxd; a Serializd failure never blocks the Letterboxd path, or vice versa
- No cookie fallback needed, Serializd's API never needs the Cloudflare cookie workaround Letterboxd sometimes does (see Cloudflare issues below), so TV sync has nothing to babysit
- Watchlist sync, import your Letterboxd or Serializd watchlist as a Jellyfin playlist (Serializd also gets a Jellyfin collection for the shows themselves)
- Seerr integration, auto-request watchlisted films or shows missing from your library, attributed to the right user; optionally backfill requests for titles that arrived outside Seerr, and mirror your Letterboxd or Serializd watchlist into Seerr
- Dashboard, sync stats, activity history, and one-click sync from the plugin page
- Send logs to developer, one-click diagnostic bundle from the Logs tab, with a full preview of what's sent and a reference code to quote in a bug report
- Cloudflare resilient, automatic retry with backoff on rate limits and transient Letterboxd errors, raw cookie fallback
- In Jellyfin, go to Dashboard > Plugins > Repositories
- Add the File Transformation repository (required for the sidebar link):
- Name:
File Transformation - URL:
https://www.iamparadox.dev/jellyfin/plugins/manifest.json
- Name:
- Add the Jellyscribe repository:
- Name:
Jellyscribe - URL:
https://lbsync-telemetry.lachlanbyoung.workers.dev/manifest.json
- Name:
- Go to Catalog, install File Transformation, then install Jellyscribe
- Restart Jellyfin
- Hard-refresh the Jellyfin web UI (Ctrl/Cmd + Shift + R) so the new sidebar link loads
- Install the File Transformation plugin first (see iamparadox27/Jellyfin.Plugin.FileTransformation), required for the sidebar link to appear
- Download the latest Jellyscribe ZIP from Releases
- Extract
Jellyscribe.dllandHtmlAgilityPack.dllto your Jellyfin plugins directory - Restart Jellyfin
- Go to Dashboard > Plugins > Jellyscribe
- Switch to the Settings tab
- Click + Add Account
- Select your Jellyfin user, enter your Letterboxd username and password
- Check Enabled
- Click Save
That's it. Watch a movie and check your Letterboxd diary.
Adding a Serializd account works the same way, just enter your Serializd email/username and password instead; a Jellyfin user can link a Letterboxd account, a Serializd account, or both.
These apply the same way whether the account is a Letterboxd (film) or Serializd (TV) account, just scoped to the matching media type.
| Setting | Description |
|---|---|
| Enabled | Master switch for this account; nothing syncs while unchecked, saved settings are kept |
| Favorites as liked | Marks the title as "liked" on Letterboxd or Serializd if favorited in Jellyfin |
| Recently played only | Limits daily catch-up to titles played in the last N days |
| Primary account | When one Jellyfin user links multiple accounts on the same service, the primary wins on rating-import conflicts and is preselected in the review modal |
| Watchlist to playlist | Mirrors your Letterboxd or Serializd watchlist into a Jellyfin playlist daily; each account gets its own playlist (name configurable) |
| Auto-request via Seerr | Watchlisted films or shows missing from your library are requested in Seerr, attributed to this user's Seerr account (set the Seerr URL and API key above the account list) |
| Backfill available requests | Extends auto-request to titles already in the library that have no request record, so titles that arrived outside Seerr still show a requester; never triggers re-downloads |
| Mirror into Seerr watchlist | Two-way mirror of your watchlist into your Seerr user's own watchlist (movies for Letterboxd accounts, TV for Serializd accounts) |
| Import diary as played | Marks Jellyfin movies or episodes as played if they appear in your Letterboxd or Serializd diary |
| Skip previously synced | Uses the plugin's local sync history to skip titles already logged without hitting Letterboxd/Serializd; recommended, especially on large libraries |
| Stop on failure | Halts the run at the first failure to avoid inflaming rate limits; the rest are picked up next run |
| Raw Cookies | For Cloudflare bypass, Letterboxd accounts only, see below |
The Dashboard tab shows the same for both Letterboxd and Serializd accounts:
- Sync statistics (total, synced, rewatches, skipped, failed, requested)
- Recent activity with links to each title on Letterboxd or Serializd
- Run Sync Now button to trigger a sync on demand
- Review buttons to write and post reviews directly to Letterboxd or Serializd
If login fails with a 403 error:
- Log into Letterboxd in your browser
- Open DevTools (F12) > Network tab
- Reload and click any request to
letterboxd.com - Copy the Cookie header value (everything after
Cookie:, not the label itself) - Paste it into the Raw Cookies field
- Copy the User-Agent request header value from the same request and paste it into the User-Agent field
Important: Cloudflare ties cf_clearance to the exact User-Agent that solved the challenge. If you copied cookies from Chrome but leave the User-Agent field blank, the plugin sends the default Firefox UA and Cloudflare will reject the cookie. Always paste the User-Agent from the same browser you copied the cookies from. Leave it blank only if you copied cookies from Firefox 134 on Windows.
When a correctly-copied cookie still gets blocked, it's usually one of these:
-
Different IP. Cloudflare pins
cf_clearanceto the IP address that solved the challenge, not just the User-Agent. If the box running Jellyfin reaches the internet via a different public IP than the browser did (different machine, VPN, mobile tether, a server in a datacenter), Cloudflare sees the token arrive from a new IP and rejects it. Fix: paste fresh cookies from a browser running on the same network as the Jellyfin server, and watch out for VPNs or split tunnels. -
It expired.
cf_clearancefrom a managed challenge is short-lived, often around 30 minutes. If there's a gap between copying the cookies and the sync actually running, the token can already be dead. Fix: paste fresh cookies and immediately trigger a sync from the plugin dashboard rather than waiting for the scheduled run. -
Connection fingerprint. Cloudflare doesn't only check the cookie and UA, it also fingerprints the TLS handshake and HTTP/2 behaviour of the connection. A plugin's HTTP client doesn't look like a real browser at that layer, so on a site running bot-fight mode the right cookie isn't always enough on its own. There isn't much the plugin can do about this one.
If you've ruled all three out and a single film keeps getting stuck on the TMDb lookup, open an issue. A workaround that skips the Cloudflare-protected lookup for that one film (pointing a TMDb ID directly at a Letterboxd slug) is being considered.
The plugin can send anonymous, opt-in usage telemetry. It is off by default, nothing is ever sent unless you enable it (one-time dashboard banner or the Settings checkbox).
When enabled, one small ping is sent per week, plus one extra ping (capped at one per day) when sync errors start occurring so fleet-wide breakage gets caught early. The full payload is exactly this, you can see your own at any time via Settings → Anonymous Telemetry → Preview exact JSON:
{
"schema_version": 1,
"instance_id": "8a6f4f6e-1f2b-4c43-9a57-2f0e6f3b9d1c",
"ping_type": "weekly",
"plugin_version": "2.1.0.0",
"jellyfin_version": "10.11.11",
"features": { "watchlist_sync": true, "diary_import": false, "tv_watchlist_sync": false, "tv_diary_import": false,
"...": "booleans of which Letterboxd and Serializd settings are enabled" },
"buckets": { "accounts": "1", "library": "2k-10k", "syncs_per_week": "1-10", "syncs_ever": "11-100",
"tv_syncs_per_week": "0", "tv_syncs_ever": "0" },
"errors": { "cloudflare_403": 0, "auth_failure": 0, "tmdb_lookup": 0, "jellyseerr_error": 0, "rate_limit": 0, "other": 0,
"state": { "cloudflare_403": false, "...": "which error types are currently occurring" } }
}The precise promise, worded carefully:
- No IPs, usernames, film titles, library content, or exact counts ever enter the dataset. Counts are reported in buckets only. (Transport logs at the hosting platform retain caller IPs for the platform's own short retention window, like any HTTPS service; they are never stored in the telemetry dataset.)
- The instance ID is random, generated when you opt in, never derived from your hardware, network, or Jellyfin install. Regenerate it any time in Settings: future pings get a fresh identity. Old rows remain (unlinked going forward); at small fleet sizes configuration similarity could in principle still allow correlation, so the honest claim is "unlinked", not "erased".
- The "Preview exact JSON" modal doubles as a diagnostic bundle for bug reports. It contains your instance ID, pasting it into a public issue links that ID to your past pings, which is why the modal offers Copy + regenerate ID.
- Disabling telemetry stops all pings immediately.
What it's for: deciding what gets built next based on what people actually use, and an automated canary that compares error rates across releases and files regression issues before bug reports arrive.
The recommended plugin repository URL and the release downloads are served through an edge-cached mirror of the GitHub manifest. The mirror counts each request as an anonymous, weekly-rotating hash of the caller's IP so the project can estimate how many servers run the plugin. This is a plain traffic count, not the telemetry above: no instance ID, no settings, no versions beyond the release being downloaded, and the raw IP is never stored, the hash is salted and cannot be linked across weeks by design.
Since v1.19.0 the plugin also adds the mirror as a second catalog repository entry (named "... (mirror)") alongside your existing GitHub entry, once. Your GitHub entry is never removed, so updates keep working even if the mirror is unreachable. If you prefer not to be counted, delete the mirror entry, the plugin will not re-add it, and both entries serve the identical manifest.
When something goes wrong, the Logs tab has a Send logs to developer button. It packages the recent Jellyscribe log lines shown on that tab (passwords, cookies, and auth tokens are never logged) plus an anonymous telemetry snapshot, uploads them privately, and gives you a short reference code (e.g. LBX-7Q2F9K) to quote if you open a bug report.
Unlike the anonymous telemetry above, logs are not anonymous, they can contain your Letterboxd username or film titles, and the bundle is linked to your telemetry instance ID. So it is strictly opt-in per use: a confirmation step spells this out, lets you add a note describing the problem, and offers a preview of exactly what is sent before anything leaves your server. Works whether or not telemetry is enabled. Uploaded bundles are stored privately and auto-deleted after 90 days.
- Jellyfin 10.11+ (current releases are also known to run on the Jellyfin 12.0 release candidates; official 12.0 support will be declared once 12.0 stable ships and passes verification, see
openspec/changes/add-jellyfin-12-support/)- Migrating your server to Jellyfin 12? It is safe to follow Jellyfin's advice and remove the plugin first: your accounts, settings, and sync history all survive a reinstall from the catalog.
- A Letterboxd and/or Serializd account
- File Transformation plugin, required for the Jellyscribe link to appear in the Jellyfin sidebar (everything else works without it)
- Optional: a Seerr instance for the auto-request and watchlist-mirror integrations
git clone https://github.com/builtbyproxy/Jellyscribe.git
cd Jellyscribe
dotnet build -c ReleaseOutput Jellyscribe.dll is in LetterboxdSync/bin/Release/net9.0/.
PRs welcome. A few conventions:
- PR body shape lives in
.github/pull_request_template.md. Symptom first, plain English, six fixed sections (What's broken, Why it happens, What this PR does, How to test, Follow-ups). - Non-trivial changes are planned through
openspec/before implementation: proposal, design, specs, then tasks. Seeopenspec/changes/for active proposals and thearchive/folder for past ones.
