Dynamically generated PNG cards showing what you last watched on Trakt.tv — embeddable in any README, website, or profile.

Trakt Widgets is an image API that generates real-time "last watched" cards from any public Trakt.tv profile. The endpoint returns a PNG — not JSON, not HTML — so you can drop it as an <img> tag anywhere images are accepted: GitHub READMEs, personal sites, Notion pages, or profile readmes. It updates automatically every time the URL is fetched.
Overview
The core idea is dead simple: an Express server exposes /:username/watched.png. When that URL is hit, it fetches the user's watch history from the Trakt.tv API, pulls the matching backdrop from TMDB, composites title text and metadata on top using @napi-rs/canvas, and streams the PNG response directly. No pre-rendering, no scheduled jobs, no storage — every request generates a fresh card on the fly.
The companion web UI is a dark-themed landing page where users can preview their card and copy the embed snippet.
The widget URL is a plain image. It works anywhere images work — no JavaScript required on the consuming side. GitHub Markdown renders it inline in READMEs without any special configuration.
Usage
Paste this into any GitHub README. Replace your-username with your public Trakt.tv username. The card updates every time someone views the README.
Features
| Feature | Details |
|---|---|
| PNG image API | /:username/watched.png returns a fully rendered image, not JSON |
| Trakt.tv integration | Fetches the most recent watched movie or episode from the user's public history |
| TMDB backdrop | Pulls a high-resolution landscape backdrop for the watched title |
| Server-side canvas rendering | @napi-rs/canvas composites text, backdrop, and overlay server-side |
| Zero client-side JS needed | Works in any <img> tag — Markdown, HTML, Notion, anywhere |
| Self-hostable | Runs on any Node.js host; environment variables configure both API keys |
| Dark UI with glassmorphism navbar | Landing page for previewing and copying the embed snippet |
Tech Stack
| Tool | Role |
|---|---|
| Express | HTTP server, route handling, PNG response streaming |
| @napi-rs/canvas | Server-side canvas rendering for PNG card composition |
| Trakt.tv API | Public watch history — no OAuth needed for public profiles |
| TMDB API | High-resolution movie and TV show backdrops |
| Tailwind CSS | UI styling via CDN; no build step on the frontend |
How the Card is Generated
src/routes/watched.js
import { createCanvas, loadImage } from "@napi-rs/canvas"
import { getLastWatched } from "../services/trakt.js"
import { getBackdrop } from "../services/tmdb.js"
export async function watchedCard(req, res) {
const { username } = req.params
const watched = await getLastWatched(username)
const backdropUrl = await getBackdrop(watched)
const canvas = createCanvas(400, 200)
const ctx = canvas.getContext("2d")
// Draw backdrop
const backdrop = await loadImage(backdropUrl)
ctx.drawImage(backdrop, 0, 0, 400, 200)
// Gradient overlay
const gradient = ctx.createLinearGradient(0, 0, 0, 200)
gradient.addColorStop(0, "rgba(0,0,0,0)")
gradient.addColorStop(1, "rgba(0,0,0,0.85)")
ctx.fillStyle = gradient
ctx.fillRect(0, 0, 400, 200)
// Title text
ctx.fillStyle = "#ffffff"
ctx.font = "bold 18px Inter"
ctx.fillText(watched.title, 16, 165)
// Subtitle
ctx.fillStyle = "#ef4444" // Trakt red
ctx.font = "13px Inter"
ctx.fillText(watched.subtitle, 16, 185)
res.setHeader("Content-Type", "image/png")
res.setHeader("Cache-Control", "no-cache, max-age=0")
canvas.createPNGStream().pipe(res)
}Environment Configuration
| Variable | Description |
|---|---|
TRAKT_CLIENT_ID | Trakt.tv application client ID (create one at trakt.tv/oauth/applications) |
TMDB_API_KEY | TMDB v3 API key (free at themoviedb.org) |
PORT | Optional — defaults to 3000 |
Both API keys are free to obtain. Trakt.tv public history requires no user OAuth — only a client ID for rate-limiting identification.