Automatically fetches synced lyrics for songs that don't have it, and provides additional functionality like romanization for all scripts and translation into 132 languages — right inside Spotify
Coming Soon
|
Ultimate Triple View Original, Romanized, and Translated text all in a single, perfectly synced frame. Stacked lyrics ( 君を愛してる / Kimi o aishiteru / I love you)
|
demo.mp4
Spotify's lyrics panel has two problems: it sometimes can't find lyrics at all, and when it does, it often can't help you understand them. Spotify Karaoke fixes both.
Smart lyrics fetching: When Spotify shows "Lyrics not available," the extension quietly searches YouTube Music and LRCLIB in the background. If synced lyrics exist anywhere, they get injected directly into the Spotify lyrics panel — perfectly timed, visually integrated with Spotify's native UI. No user action required.
Romanization & Translation: Once lyrics are on screen — whether fetched or native — you can switch between three display modes:
- Original — lyrics as Spotify shows them, unchanged.
- Romanized — any non-Latin script (Japanese, Korean, Arabic, Indic, Thai, etc.) rendered phonetically in the Latin alphabet for instant sing-along.
- Translated — lyrics translated into any of 132 languages.
Switch between modes using the floating pill controls injected directly into the lyrics panel, the popup, or keyboard shortcuts. No page reload, no flicker.
Native script restoration: For global non-Latin scripts (Hindi, Thai, Arabic, CJK, etc.), Spotify often serves low-quality romanized fallback lyrics even when the original native-script version exists on Musixmatch. This extension automatically intercepts and restores the original, high-fidelity native script — before Spotify even renders the page. Romanize and Translate modes then operate on the correct native source, producing significantly more accurate results.
Dual Lyrics mode — in Romanized or Translated mode, the processed text becomes the primary karaoke highlight line, with the original script shown below in a smaller font for reference (suppressed when identical to the primary line).
Genetic Lock Shield — keeps the lyrics panel permanently accessible, even for tracks where Spotify deliberately disables the lyrics button (e.g., instrumentals, podcasts).
Ad Intermission HUD — includes a custom 'Intermission' screen during audio ads to maintain a seamless visual experience without breaking the UI.
Cross-device Sync — preferences like your target language and display mode automatically sync across all your devices via storage.sync.
| Dual Lyrics On | Dual Lyrics Off |
|---|---|
![]() |
![]() |
| Original (Korean) | Romanized |
|---|---|
![]() |
![]() |
| Translated |
|---|
![]() |
There are three ways to switch between Original, Romanized, and Translated:
| Method | How |
|---|---|
| Floating pill | The [Original] [Romanized] [Translated] pill injected at the top of the Spotify lyrics panel. |
| Extension popup | The same pill is replicated inside the popup - acts as a remote control and always reflects the current mode, even if the floating pill is hidden. |
| Keyboard shortcuts | While the lyrics panel is open, press O (Original), R (Romanized), or T (Translated). Safe to use - shortcuts are ignored when focus is in a text input or search bar. |
Note
Floating controls and keyboard shortcuts only function when Spotify's lyrics panel is open. Click the microphone icon in the player to open it.
Power users can toggle off the floating pill entirely via Show Floating Controls in the popup, then use keyboard shortcuts or the popup pill for a completely unobstructed lyrics view.
| Floating Controls On | Floating Controls Off |
|---|---|
![]() |
![]() |
- Chrome Web Store — Chrome, Brave, and other Chromium browsers.
- Firefox Add-ons — Mozilla Firefox (≥ 142.0 — requires MV2 with
browser.storage.sessionsupport). - Microsoft Edge Add-ons — Microsoft Edge.
If you wish to test the latest features before they hit the stores, you can install the extension manually.
- Download the latest release
.zipfrom the Releases page and extract it. - Open your browser and navigate to
chrome://extensions/. - Enable Developer mode (usually a toggle in the top-right corner).
- Click Load unpacked and select the folder where you extracted the extension.
Important
Firefox Limitation: Temporary add-ons are removed when the browser restarts. For a permanent install, please use the Firefox Add-ons store.
- Download and extract the latest release
.zip. - Open Firefox and navigate to
about:debugging#/runtime/this-firefox. - Click Load Temporary Add-on....
- Select the
manifest.jsonfile inside the extracted folder.
- Download and extract the latest release
.zip. - Navigate to
edge://extensions/. - Enable Developer mode (toggle in the bottom-left sidebar).
- Click Load unpacked and select the folder where you extracted the extension.
Spotify often serves romanized fallback lyrics for non-Latin songs (e.g. Thai, Arabic, or Indian languages) even when the original native-script version exists on Musixmatch.
Spotify Karaoke fixes this automatically. When you play a supported song, the extension intercepts Spotify's lyrics API response, detects the romanized fallback, fetches the native-script subtitles from Musixmatch, and replaces the response before Spotify renders it. The original script appears natively in the lyrics panel — no user action required.
Romanize and Translate modes then operate on the correct native source, producing significantly more accurate results.
Supported coverage: Deep restoration and optimization for all major non-Latin scripts globally (Tamil, CJK, Hindi, Arabic, Thai, Cyrillic, Hebrew, etc.).
Forensic Verification: Before injecting any Musixmatch payload, the extension runs a character-level audit — counting native script characters (Hangul, Devanagari, CJK, etc.) against Latin characters in the response. If Musixmatch itself returned a romanized fallback (a known edge case for some tracks), the injection is silently aborted, preventing the extension from overwriting Spotify's romanized lyrics with a different romanized version from a different source.
| Script | Library | Engine |
|---|---|---|
| Universal (fallback) | transliteration |
Local |
| Japanese (Kanji + Kana) | @sglkc/kuroshiro + Kuromoji |
Local |
| Korean (Hangul) | @romanize/korean |
Local |
| Chinese (Hanzi) | pinyin-pro |
Local |
| Tamil | tamil-romanizer |
Local |
| Indic (Devanagari, Telugu, Gujarati, Gurmukhi, Kannada, Odia) | @indic-transliteration/sanscript |
Local |
| Cyrillic (Russian, Ukrainian, Bulgarian, Serbian, Belarusian) | cyrillic-to-translit-js |
Local |
| Thai | @dehoist/romanize-thai |
Local |
| Malayalam, Bengali, Arabic, Hebrew | Google Translate (dt=rm) |
API |
| Translation (All 132 languages) | Google Translate → MyMemory fallback | API |
⚡ Local — zero latency, runs entirely in your browser. 🌐 API — requires a network call.
| Component | Detail |
|---|---|
| Smart lyrics fetch | YouTube Music synced → LRCLIB synced → YouTube Music plain → LRCLIB plain |
| Pre-fetch racing | On every track change, the extension races Spotify to fetch lyrics and stores the result in a 10-minute sliding-window registry — so the hijack decision is already made before Spotify finishes rendering |
| Local romanization | 10 bundled libraries covering 16+ writing systems |
| Interception point | document_start, MAIN world — before React first paint |
| Cache | L1: 10-song RAM · L2: 200-song persistent (browser.storage.local, LRU-evicted) · L3: in-flight deduplication · L4: network |
| Cache upgrades | Background worker silently re-queries YTM weekly for previously unsynced songs; upgrades to synced automatically if found |
| Forensic Verifier | Before injecting a Musixmatch payload, counts non-Latin Unicode characters to confirm a true native script was returned — aborts injection if Musixmatch also returned a romanized fallback, preventing loops |
| React Fiber bridge | slyBridge.ts scans Spotify's Fiber tree at 500ms intervals. Utilizes a target-optimized Crawler Cache Gate to completely stand down heavy signature scans once resolved, maintaining a virtually 0% CPU footprint during active lyrics display |
| Genetic Lock | Object.defineProperty override on Spotify's internal lyrics button disabled prop + extraction of toggleLyrics() directly from memoized Fiber props — keeps the panel permanently accessible and programmatically controllable |
| Playback sync | Wall-clock extrapolation via performance.now() for 60fps sub-second accuracy between Spotify's ~500ms UI ticks |
| Stale-cancel guards | Generation Map + processGen parity counter (2 independent mechanisms) |
| Translation fallback | Google Translate → MyMemory → original preserved |
| Browser support | Chrome MV3 · Firefox MV2 (≥ 142.0) |
Requirements: Node.js 18+
git clone https://github.com/haroldalan/spotify-karaoke.git
cd spotify-karaoke
npm install
npm run dev # Chrome (live reload)
npm run dev:firefox # Firefox (live reload)
npm run dev:edge # Edge (live reload)
npm run build # Production build - Chrome
npm run build:firefox # Production build - Firefox
npm run build:edge # Production build - Edge
npm run zip # Package for Chrome Web Store submission
npm run zip:firefox # Package for Firefox Add-ons submission
npm run zip:edge # Package for Edge Add-ons submission
npm run test # Run unit and component test suiteentrypoints/
background.ts # Service worker: lyrics fetching, romanization + translation orchestration, 4-layer cache
fetchInterceptor.ts # MAIN world unlisted script: fetch interceptor, native script restoration
slyBridge.ts # MAIN world unlisted script: React Fiber scanner, Genetic Lock Shield, state broadcaster
spotify-lyrics.content/
index.ts # Content script: bootstrapper, MutationObserver, mode switching, slyBridge wiring
style.css
popup/ # Preact popup: mode pill, language selector, dual lyrics + visibility toggles
lib/
core/ # Pipeline B: lifecycle, mode controller, lyrics fetching, state store
dom/ # Pipeline B: DOM surgery, lyrics observer, controls pill, toast
slyCore/ # Pipeline A: custom DOM engine, playback sync, scavenger, status HUD, ad manager
lyricsProviders/ # YTM scraper, LRCLIB client, 4-layer cache
lyrics/ # Script detector, local romanizer, lyrics processor
translate/ # Google Translate API, MyMemory fallback
mxmClient.ts # Musixmatch client for native script restoration
Spotify Karaoke runs two parallel pipelines depending on what Spotify provides.
Technical deep-dive
Pipeline B — Native lyrics exist: A MutationObserver watches document.body for song key updates (aria-label) and newly rendered lyric lines. The observer processes mutations in two passes: Pass 1 handles song key updates to ensure state coherence, and Pass 2 handles DOM structure changes to detect lyric injection. This prevents race conditions where lyrics might be processed against the previous song's key.
When lyrics are detected, the engine reads the current mode (Original / Romanized / Translated), fetches processed lyrics from cache or sends a PROCESS message to the background worker, and writes the result back into the existing DOM elements — specifically overwriting Spotify's React Fiber nodes with sly-main-line and sly-dual-line <span> elements for Dual Lyrics rendering. Spotify's own React state is never touched.
Pipeline A — Lyrics missing or broken: When Spotify reports "Lyrics not available," the slyCore engine takes over. It hides Spotify's native container entirely and injects a custom #lyrics-root-sync container in its place. Lyrics are fetched from YouTube Music or LRCLIB via the background service worker, then rendered with a requestAnimationFrame loop using wall-clock extrapolation for 60fps sync accuracy — completely decoupled from Spotify's own UI refresh cycle.
Pre-fetch racing: The moment a track change is detected, the extension races Spotify to fetch external lyrics and stores the result in a 10-minute sliding-window registry (preFetch). By the time Spotify finishes rendering its UI, the extension already knows whether a fallback hijack is needed — making the transition feel instant.
Romanization & translation: The background service worker receives an array of lyric strings, detects the script using Unicode range scoring, routes to the appropriate local library or Google Translate batch API, and returns both a translated array and a romanized array in a single response.
Native script restoration: entrypoints/fetchInterceptor.ts is compiled as an unlisted script and registered in the extension manifest to run in the MAIN world at document_start. This ensures the interceptor is active before Spotify's application bundle even begins to execute. It monkey-patches window.fetch to intercept color-lyrics/v2/track/* responses, fetches the native-script version from Musixmatch, and runs a Forensic Verifier — counting non-Latin Unicode characters to confirm Musixmatch returned a true native script and not another romanized fallback — before swapping out Spotify's response. The swap happens before React renders, so Spotify's app is entirely unaware it received injected data.
React Fiber bridge: entrypoints/slyBridge.ts runs in the MAIN world and traverses Spotify's React Fiber tree to extract live track metadata, access tokens, and the user's queue. It also maintains a Genetic Lock — an Object.defineProperty override on the internal disabled prop of Spotify's lyrics button that keeps it permanently enabled, even for tracks where Spotify would otherwise disable it. It additionally extracts Spotify's internal toggleLyrics() function directly from memoized Fiber props so the extension can open the lyrics panel programmatically.
Automatic cache upgrades: If a song was previously cached with only unsynced lyrics, the background worker silently re-queries YouTube Music once a week to check whether a synced version has since become available, and automatically upgrades the local cache if so — with no user action required.
Why aren't lyrics showing? Ensure the native Spotify lyrics panel is open. The extension injects into the native UI — click the microphone icon in the player bar to open it.
The lyrics panel is stuck or glitching. If Spotify changes its internal class names or state becomes corrupt, open the extension popup and click Reset Data. This safely clears the local cache and resets your preferences.
A song is missing lyrics entirely. When the extension cannot find lyrics on YouTube Music or LRCLIB, it will show a "Lyrics Not Found" screen. You can help the community by clicking the Contribute to LRCLIB link on that screen to upload the missing lyrics yourself.
I just uploaded lyrics, but they still aren't showing up! To keep things fast, the extension caches the "missing" state for 7 days. If you've just contributed lyrics and want to see them immediately, open the extension popup and click Reset Data, then refresh Spotify to force a fresh search.
Issues, ideas, and PRs are welcome — especially for romanization accuracy improvements or new script support.
- Fork the repository.
- Create a feature branch:
git checkout -b feature/your-feature - Commit your changes:
git commit -m 'feat: describe what you added' - Push and open a Pull Request.
Please keep PRs focused. One feature or fix per PR makes review much faster.
Note
No personal data is collected. Settings are stored in browser.storage.sync. Processed lyrics are cached locally in browser.storage.local and never leave your device. Lyric text is sent to Google Translate or MyMemory only when using Translated or API-based Romanized modes. See Google's Privacy Policy and MyMemory's Terms.
Warning
Spotify Karaoke is not affiliated with or endorsed by Spotify AB. It is an independent open-source project that modifies the Spotify web player UI for personal and accessibility use.
If you encounter any bugs, have suggestions for new features, or just want to chat about the project, feel free to reach out!










