DEV Community

VoiceDeveloper
VoiceDeveloper

Posted on

Build a Text-to-Speech Chrome Extension

Why a Text‑to‑Speech Chrome Extension?

If you spend a lot of time reading articles, documentation, or even code comments, a quick “read‑aloud” button can be a game‑changer. Chrome extensions are perfect for injecting that functionality directly into the browser UI without needing a separate app. In this guide we’ll build a lightweight extension that sends selected text to the ElevenLabs API, receives a natural‑sounding audio clip, and plays it back instantly.

Pro tip: ElevenLabs offers high‑quality voice cloning and real‑time TTS, making it a solid choice for developers who want studio‑grade audio without the heavy lifting.


What You’ll Need

Item Why
A Google Chrome (or Chromium‑based) browser To test and load the extension
A free ElevenLabs API key To generate the speech audio
Basic knowledge of JavaScript, HTML, and Chrome’s Manifest V3 To glue everything together
A text editor (VS Code works great) For writing the code

Grab your ElevenLabs API key from the dashboard after signing up via this link: https://try.elevenlabs.io/kr07zfuqn1bp. The link is affiliate‑enabled, so you’ll be supporting the tutorial while getting a generous free tier.


Project Structure

tts-extension/
│
├─ manifest.json
├─ popup.html
├─ popup.js
├─ background.js
└─ icons/
   └─ speak-48.png
Enter fullscreen mode Exit fullscreen mode

We’ll keep things simple: a popup UI, a background service worker that talks to ElevenLabs, and a small icon for the toolbar button.


Step 1: Create manifest.json

Chrome’s Manifest V3 is the modern way to declare permissions and assets.

{
  "name": "Quick TTS Reader",
  "description": "Read selected text aloud using ElevenLabs AI voices.",
  "version": "1.0",
  "manifest_version": 3,
  "action": {
    "default_popup": "popup.html",
    "default_icon": {
      "48": "icons/speak-48.png"
    }
  },
  "permissions": [
    "activeTab",
    "scripting",
    "storage"
  ],
  "background": {
    "service_worker": "background.js"
  },
  "host_permissions": [
    "https://api.elevenlabs.io/*"
  ]
}
Enter fullscreen mode Exit fullscreen mode

Key points:

  • activeTab lets us inject a tiny script to grab the user’s selection.
  • scripting is required for programmatic injection in Manifest V3.
  • host_permissions whitelist the ElevenLabs endpoint.

Step 2: Build the Popup UI

popup.html is the tiny window that appears when you click the extension icon.

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>Read Aloud</title>
  <style>
    body { font-family: system-ui, sans-serif; width: 250px; padding: 10px; }
    button { width: 100%; padding: 8px; margin-top: 8px; }
    select, input { width: 100%; margin-top: 4px; }
  </style>
</head>
<body>
  <h4>Quick TTS</h4>
  <select id="voiceSelect"></select>
  <button id="speakBtn">Speak Selection</button>

  <script src="popup.js"></script>
</body>
</html>
Enter fullscreen mode Exit fullscreen mode

We’ll populate the voice dropdown dynamically, then send the selected text to the background script.


Step 3: Hook Up the Popup Logic

popup.js grabs the active tab’s highlighted text and forwards it.

// popup.js
document.addEventListener('DOMContentLoaded', async () => {
  const voiceSelect = document.getElementById('voiceSelect');
  const speakBtn = document.getElementById('speakBtn');

  // Load available voices from ElevenLabs (cached locally)
  const storedVoices = await chrome.storage.local.get('voices');
  if (storedVoices.voices) {
    populateVoices(storedVoices.voices);
  } else {
    // Ask background to fetch them
    chrome.runtime.sendMessage({type: 'GET_VOICES'}, (response) => {
      populateVoices(response.voices);
      chrome.storage.local.set({voices: response.voices});
    });
  }

  function populateVoices(voices) {
    voiceSelect.innerHTML = '';
    voices.forEach(v => {
      const opt = document.createElement('option');
      opt.value = v.voice_id;
      opt.textContent = v.name;
      voiceSelect.appendChild(opt);
    });
  }

  speakBtn.addEventListener('click', async () => {
    // Inject a tiny content script to read the selection
    const [{ result }] = await chrome.scripting.executeScript({
      target: { tabId: (await chrome.tabs.query({active: true, currentWindow: true}))[0].id },
      func: () => window.getSelection().toString()
    });

    const text = result.trim();
    if (!text) {
      alert('Please select some text on the page first.');
      return;
    }

    chrome.runtime.sendMessage({
      type: 'SPEAK',
      text,
      voiceId: voiceSelect.value
    });
  });
});
Enter fullscreen mode Exit fullscreen mode

The script does three things:

  1. Fetches the list of voices (cached after the first call).
  2. Extracts the user’s highlighted text via chrome.scripting.executeScript.
  3. Sends a message to the background worker to perform the TTS request.

Step 4: Background Service Worker – The Real Workhorse

background.js handles the network request to ElevenLabs, receives the audio buffer, and plays it.

// background.js
const ELEVEN_API = 'https://api.elevenlabs.io/v1';
let apiKey = ''; // We'll fill this from storage once at startup

// Load API key from Chrome storage (set manually via the extension options or devtools)
chrome.storage.sync.get('elevenKey', (data) => {
  apiKey = data.elevenKey || '';
});

chrome.runtime.onMessage.addListener((msg, sender, sendResponse) => {
  if (msg.type === 'GET_VOICES') {
    fetch(`${ELEVEN_API}/voices`, {
      headers: { 'xi-api-key': apiKey }
    })
    .then(r => r.json())
    .then(data => sendResponse({voices: data.voices}))
    .catch(() => sendResponse({voices: []}));
    return true; // keep the message channel open
  }

  if (msg.type === 'SPEAK') {
    synthesize(msg.text, msg.voiceId);
  }
});

async function synthesize(text, voiceId) {
  if (!apiKey) {
    console.error('ElevenLabs API key missing. Set it in chrome.storage.sync.');
    return;
  }

  const payload = {
    text,
    voice_settings: { stability: 0.75, similarity_boost: 0.85 }
  };

  try {
    const resp = await fetch(`${ELEVEN_API}/text-to-speech/${voiceId}`, {
      method: 'POST',
      headers: {
        'Content-Type': 'application/json',
        'xi-api-key': apiKey
      },
      body: JSON.stringify(payload)
    });

    const arrayBuffer = await resp.arrayBuffer();
    const blob = new Blob([arrayBuffer], { type: 'audio/mpeg' });
    const url = URL.createObjectURL(blob);
    const audio = new Audio(url);
    audio.play();
  } catch (e) {
    console.error('TTS request failed', e);
  }
}
Enter fullscreen mode Exit fullscreen mode

Storing the API Key

Open Chrome’s extension page (chrome://extensions), enable Developer mode, click Inspect views → background.js, and run:

chrome.storage.sync.set({ elevenKey: 'YOUR_ELEVENLABS_API_KEY' });
Enter fullscreen mode Exit fullscreen mode

Replace 'YOUR_ELEVENLABS_API_KEY' with the key you got from the affiliate link above. This keeps the secret out of the source code.


Step 5: Test It Locally

  1. Open chrome://extensions.
  2. Enable Developer mode (top‑right toggle).
  3. Click Load unpacked and select the tts-extension folder.
  4. Pin the new “Quick TTS Reader” icon to the toolbar.
  5. Navigate to any article, highlight a paragraph, click the extension, choose a voice, and press Speak Selection.

You should hear a clear, natural‑sounding voice reading the text. If nothing happens, open the Console in the background view for debugging messages.


Optional: Curl Example for Quick Debugging

Sometimes you want to verify the ElevenLabs endpoint outside the extension. Here’s a one‑liner:

curl -X POST "https://api.elevenlabs.io/v1/text-to-speech/EXAMPLE_VOICE_ID" \
  -H "xi-api-key: YOUR_ELEVENLABS_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"text":"Hello, Chrome extension world!","voice_settings":{"stability":0.75,"similarity_boost":0.85}}' \
  --output hello.mp3
Enter fullscreen mode Exit fullscreen mode

Replace EXAMPLE_VOICE_ID with the ID you see in the dropdown and YOUR_ELEVENLABS_API_KEY with your key. The resulting hello.mp3 lets you listen to the raw output.


Going Further

  • Voice cloning – ElevenLabs lets you upload a short sample of your own voice and generate a custom voice_id. Swap that ID into the extension and you’ll have a personal TTS assistant.
  • Persist user preferences – Store the last‑used voice in chrome.storage.sync so the dropdown remembers your favorite.
  • Add a keyboard shortcut – In manifest.json, define "commands" to trigger the speak action with Ctrl+Shift+S.

The possibilities are endless, but the core pattern stays the same: grab text, call ElevenLabs, and play the audio.


Wrap‑Up & Call to Action

Building a Text‑to‑Speech Chrome extension is a fantastic way to dip your toes into voice AI while delivering immediate value to yourself and others. The heavy lifting of natural‑sounding speech is handled by ElevenLabs, a service that blends high‑quality voice synthesis with a developer‑friendly API.

Ready to give your browser a voice? Grab your free ElevenLabs API key today at https://try.elevenlabs.io/kr07zfuqn1bp, follow the steps above, and start turning any web page into an audiobook on the fly. Happy coding, and enjoy the sound of your own creation!

Top comments (0)