A Chrome Extension that overlays IMDb ratings on Netflix, Prime Video, and Hotstar title cards. The ratings are fetched securely from a backend proxy, keeping your API key safe.
When you browse movies and TV shows on Netflix, Prime Video, or Hotstar, a small rating badge appears in the top-left corner of each title card:
- Badge shows: IMDb rating (e.g., "8.2") or "—" if not found
- On click: Opens the IMDb page in a new tab
- On hover: Shows title, year, and vote count
- Go to omdbapi.com and request a free key
- Click the verification link in your email
- Copy your API key
- Open Chrome and go to
chrome://extensions - Enable Developer mode (toggle in the top-right)
- Click Load unpacked and select the
extensionfolder of this repo
- In Chrome extensions, find StreamRatings → Click the 3 dots → Options
- Paste your key into OMDb API Key and click Save Settings
That's it — no server needed. The extension calls OMDb directly.
Prefer keeping the key in .env? Put OMDB_API_KEY=... in .env (git-ignored) and run
npm run ext:env (or sh script/extension-env.sh). It writes extension/config.local.json
(also git-ignored), which the extension uses whenever no key is saved in Options. Reload the
extension in chrome://extensions afterwards.
If you'd rather keep the OMDb key off the browser, run the included Express server
(npm install && npm run dev, or deploy it anywhere) with OMDB_API_KEY and
STREAMRATINGS_SECRET env vars set, then leave the OMDb key blank in Options and fill in
Backend URL and StreamRatings Secret instead.
- Go to Netflix.com, PrimeVideo.com, or Hotstar.com
- Browse a page with title cards
- Wait ~2 seconds — you should see yellow badges with IMDb ratings!
- Click a badge to open IMDb, or hover to see details
No badges appearing?
- Check that your OMDb API key (or Backend URL + Secret) is set in extension options
- Open the browser console (F12) and look for error messages
- If using a backend, make sure the server is running (
/healthshould return{"ok":true}) - Wait 2-3 seconds after loading a page (extension scans on a delay)
"Invalid secret" error?
- Double-check that your STREAMRATINGS_SECRET in Replit Secrets matches what you entered in extension options
- Exact match required (case-sensitive)
Rating not found?
- Some titles have slightly different names on OMDb
- The badge will show "—" if no match is found
- This is normal for very new or obscure content
- Local cache → OMDb. Each browser keeps its own cache. On a local miss the extension asks OMDb directly with your key.
-
Frontend (Extension): Scans page for title cards, injects badges, fetches ratings
-
Backend (Replit): Receives requests with your secret key, calls OMDb API securely, caches results for 24 hours
-
Caching: Ratings are always served from the extension's cache first. A title is only re-fetched once its cache entry is due, based on release date:
Released Refreshed every Last 3 months (incl. current month) or upcoming 3 days 3–12 months ago (and series still airing) 5 days 1–5 years ago 15 days More than 5 years ago 30 days Not found on OMDb retried after 5 days If a refresh fails (network, daily limit), the previous rating keeps showing. Errors are never cached. Rules live in
extension/content_scripts/cache_policy.js. The optional backend also caches for 24 hours -
Quota-friendly: OMDb's free tier allows 1,000 requests/day, so ratings load only for cards that are at least half on screen (nothing below the fold, off the side of a carousel, or in background tabs), and lookups pause for the page once OMDb reports the daily limit
-
Rate Limiting: Max 60 requests per IP per minute
-
Security: Your OMDb API key is stored ONLY on the Replit backend, never sent to the extension or browser
To add Netflix, Prime, or Hotstar support:
- Inspect the site's title card HTML structure
- Edit the adapter in
extension/adapters/(e.g.,netflix.js):- Update
findCards()to select the title card elements - Update
extractTitle()to pull the title name - (optional) Update
extractYear()for the year
- Update
- The main script will automatically detect and use your adapter
Example adapter:
const MyServiceAdapter = {
domain: 'example.com',
findCards() {
return document.querySelectorAll('.title-card');
},
extractTitle(card) {
const titleEl = card.querySelector('.title');
return titleEl ? titleEl.textContent : null;
},
extractYear(card) {
const yearEl = card.querySelector('.year');
return yearEl ? yearEl.textContent : null;
},
getStableKey(card) {
const title = this.extractTitle(card);
return title ? title.replace(/\s+/g, '-').toLowerCase() : null;
}
};
if (window.location.hostname.includes('example.com')) {
window.StreamRatingsAdapter = MyServiceAdapter;
}extension/
manifest.json # Chrome extension config
options/
options.html # Settings page
options.js
options.css
content_scripts/
main.js # Main logic (caching, deduplication, fetch)
adapters/
netflix.js # Site-specific selectors
prime.js
hotstar.js
ui/
badge.js # Badge creation & tooltips
badge.css
background/
service_worker.js # Minimal service worker
server/ # Backend (Replit)
index.ts # Express server
routes.ts # /api/omdb endpoint
storage.ts # In-memory cache
rateLimit.ts # Rate limiting per IP
db.ts # Database (unused but available)
client/ # Landing page (setup instructions)
src/pages/
src/components/
- Concurrency: Max 5 concurrent network calls to prevent overwhelming the backend
- Deduplication: Same title requested twice = only 1 network call (shared promise)
- Caching:
- Browser: 7 days (chrome.storage.local)
- Server: 24 hours (in-memory)
- Debouncing: MutationObserver scans every 1 second (not on every DOM change)
- Sites' DOM structures can change → if badges stop appearing after a site update, adapters may need adjustment
- Very large queries (100+ cards at once) may hit rate limits; concurrency control helps
- No accounts or user data stored; purely functional caching
If you encounter issues:
- Check browser console for errors (F12)
- Verify Secrets are set correctly in Replit
- Confirm extension options are filled in
- Restart the Replit server
Enjoy your IMDb ratings! 🎬