← Station

Letterboxd Has Two Poster URL Schemes, and the Film ID Won't Tell You Which

BLIP · · Engineering · 2 min read

Adding film posters to the telemetry page, I tried to reconstruct Letterboxd's CDN URLs from the film id and slug. Three of four worked; Memories of Murder 403'd at every size. The reason: TMDB-imported posters live under a totally different path — /sm/upload/{hash}/ with no id and no slug — so they're unconstructable. The honest source is the film page's JSON-LD image field.

I wanted real posters on the WATCHING tab of /telemetry. Letterboxd has no public API, but its poster CDN is just static files, so the obvious move is to reconstruct the URL. The pattern looks deterministic:

https://a.ltrbxd.com/resized/film-poster/{id-split-by-digit}/{id}-{slug}-0-500-0-750-crop.jpg?v={key}

The film id sits in data-resolvable-poster-path on the profile page ("uid":"film:45312"), the slug is in the URL, the cache key is right there too. I built four URLs and HEAD-tested them:

favouriteidconstructed URL
The Tatami Galaxy590085200
Love in a Puff23067200
Look Back1127669200
Memories of Murder45312403

Three worked — including details that shouldn’t have. Look Back’s URL slug is look-back-2024, but I learned later its real poster filename is just look-back; the constructed URL resolved anyway. Swapping in a stale ?v= key still returned 200. So the resizer keys on the numeric id and dimensions — the slug and cache-buster are cosmetic. That’s why guessing mostly works, and why it lulls you.

Then there’s Memories of Murder. 403 at every dimension. I burned an embarrassing amount of time assuming the slug was wrong, brute-forcing variants, even chasing "hasDefaultPoster":true in the JSON — which turns out to be a red herring, it’s the lazy-load placeholder state and it’s true for all four favourites.

The actual answer was in the film page’s JSON-LD:

"image":"https://a.ltrbxd.com/resized/sm/upload/84/xt/e8/mw/gawnVe9cFowdoDLo9Pok12NTw39-0-230-0-345-crop.jpg"

There’s no film id in that path. No slug. No /film-poster/ at all. TMDB-imported posters live under /resized/sm/upload/{hash}/ keyed by a TMDB filename hash. You cannot derive it from anything on the profile page — not the id, not the slug, not the cache key. Native Letterboxd posters use /film-poster/{id}/; imported ones use /sm/upload/{hash}/. Two schemes, and the id tells you nothing about which.

The honest fix is to stop reconstructing and read the resolved URL from the source. The film page’s <script type="application/ld+json"> image field has the correct path under either scheme — and, usefully, the full film page isn’t Cloudflare-challenged (only the /image-150/ AJAX fragment is). One fetch per film, then swap -0-230-0-345--0-500-0-750- for a bigger render.

The kicker: the same scheme bit me again an hour later. Recent films pull their posters from the RSS <description> <img>, and my extraction regex required the substring film-poster. Two recently-watched films — Neighbors 2 and The Girl with the Dragon Tattoo — silently rendered posterless, because their RSS posters were /sm/upload/ too. Same root cause, second crime scene.

Takeaway: when a CDN URL looks reconstructable from IDs, check whether there’s more than one path scheme before you build a generator around the happy one. Match the host, not a path prefix — and when the source already hands you the resolved URL (JSON-LD, an RSS <img>), take it instead of rebuilding it from parts.

// Discussion

Comments are powered by GitHub Discussions via Giscus. Sign in with your GitHub account to add a reply, or discuss on X.

Keyboard Shortcuts

// navigate
1 2 3
Manifest · Station · Archive
Cycle sheets
// go to (press g, then…)
g h
Home
g s
Station
g a
Artifacts
g e
Telemetry
g n
Now
g w
Watching
g r
Reading
g u
Uses
g m
Playlist
g c
Contact
g o
Colophon
// station
[ ]
Switch stream (blips / broadcasts)
/
Focus search
// reading a post
Older · newer post
k j
Older · newer post
// general
t
Cycle theme
?
Toggle this panel
Esc
Close panel