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.
Your Trakt.tv profile must be set to public. Private profiles will return no watch history and the widget will fail to generate.
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 | npm client for the Trakt API — watch history and metadata |
| TMDB API | Optional — HD movie and TV show backdrops |
| TVmaze | Fallback episode metadata when TMDB is unavailable |
| Metahub | Poster image fallback source |
| 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 | Optional — TMDB v3 API key (free at themoviedb.org), used for HD backdrops |
PORT | Optional — defaults to 3000 |
Both keys are free to obtain. Trakt.tv public history requires no user OAuth — only a client ID for rate-limiting identification. TMDB is optional; the widget falls back to TVmaze and Metahub when it is not set.
Deployment
The project includes a vercel.json pre-configured for serverless deployment. One command deploys everything:
vercel deploySet TRAKT_CLIENT_ID and optionally TMDB as environment variables in the Vercel dashboard before deploying.
Landing Page UI
The companion web interface uses Tailwind CSS (CDN — no build step) adapted from TailwindUI components.
| Element | Detail |
|---|---|
| Background | #080808 near-black base |
| Accent | Trakt red #ed1c24 |
| Hero | Split layout — screenshot on dark, SVG grid background, glow blob |
| Navbar | Glassmorphism sticky header |
| Footer | Dark minimal; shared across all pages |