Back to Projects

MP3 Extractor - Chrome Extension

Started: March 01, 2026

MP3 Extractor Extension

Extract high-quality audio from any video. One click. 1000+ platforms. View repo


The Evolution

Started as a web app for extracting audio from YouTube links. Worked well, but required copy-pasting URLs and leaving your current tab.

The Chrome extension eliminates that friction. One click from any video page.

What I Built

A Chrome extension that:

  • Detects video URLs automatically on YouTube, Vimeo, TikTok, etc.
  • Extracts MP3 audio via the Cobalt API
  • Tracks download history with timestamps
  • Provides format/quality options (MP3/WAV/OGG, 128/256/320 kbps)
  • Integrates with right-click context menu
  • Auto-downloads files directly to your Downloads folder

Stack: TypeScript, React 19, Webpack 5, Chrome Extensions API (Manifest V3)

Key Learnings

1. Manifest V3 is a different beast

Service workers replace background pages. No DOM, no XMLHttpRequest, stricter CSP. The architecture shift from web app to extension required rethinking everything.

2. Message passing is your lifeline

Popup → Service Worker → Content Script. Everything communicates via chrome.runtime.sendMessage. Type-safe message contracts saved hours of debugging.

3. chrome.downloads beats fetch every time

Initially tried downloading files in the popup. Terrible UX. Delegating to chrome.downloads.download() in the service worker is instant and native.

4. Storage APIs have quirks

chrome.storage.local has a 5MB limit. Had to cap history at 100 items and clean up old entries. IndexedDB is overkill for this use case.

5. Cobalt API > yt-dlp for extensions

The web app used yt-dlp locally. Extensions can't run shell commands. Cobalt API handles 1000+ platforms via HTTP. Same result, simpler architecture.

6. Webpack config matters

React in an extension needs careful bundling. Separate entry points for background, popup, options, content scripts. MiniCssExtractPlugin for styles. CopyPlugin for assets.

7. Users expect instant feedback

Loading states, progress indicators, success messages. The extension lives in a 600x600px popup. Every pixel counts for UX.

Architecture

Service Worker (background.js):

  • Handles all API calls to Cobalt
  • Manages chrome.downloads for file downloads
  • Stores history in chrome.storage.local
  • Routes messages from popup/content scripts

Popup UI (popup.html):

  • React 19 with custom hooks (useExtract, useHistory, useSettings)
  • Two-tab interface: Extract | History
  • Auto-detects video URLs from active tab
  • Dark theme with CSS variables from web app

Content Script:

  • Detects MP3 links in page DOM
  • Monitors for dynamically loaded content (MutationObserver)
  • Sends video URLs to service worker

Options Page:

  • Settings UI for format, bitrate, auto-download
  • Persists to chrome.storage.local

Build Process

cd chrome-extension
npm install
npm run build   # Webpack bundles to dist/
npm run package # Creates extension.zip

Output:

  • background.js (5.7 KB)
  • popup.js + popup.css (199 KB)
  • options.js + options.css (199 KB)
  • content-script.js (828 bytes)

Total: ~405 KB minified.

What's Next

Potential improvements:

  • Firefox Add-on version (Manifest V2/V3 compatibility)
  • Batch download queue
  • Playlist support (extract entire YouTube playlists)
  • Safari extension (requires different build)
  • Format conversion options beyond audio

Reflections

This project taught me more about browser extension architecture than reading docs ever could. The constraints force you to think differently:

  • No server. No database. Just service workers and storage APIs.
  • No hot reload. Manual reload on chrome://extensions every change.
  • Strict permissions model. Every API must be justified.

The result? A tool I actually use daily. From "paste URL in web app" to "right-click → Extract Audio" is a massive UX improvement.

Building for yourself reveals what actually matters.


Links