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
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/*"
]
}
Key points:
-
activeTablets us inject a tiny script to grab the user’s selection. -
scriptingis required for programmatic injection in Manifest V3. -
host_permissionswhitelist 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>
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
});
});
});
The script does three things:
- Fetches the list of voices (cached after the first call).
-
Extracts the user’s highlighted text via
chrome.scripting.executeScript. - 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);
}
}
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' });
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
- Open chrome://extensions.
- Enable Developer mode (top‑right toggle).
- Click Load unpacked and select the
tts-extensionfolder. - Pin the new “Quick TTS Reader” icon to the toolbar.
- 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
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.syncso the dropdown remembers your favorite. -
Add a keyboard shortcut – In
manifest.json, define"commands"to trigger the speak action withCtrl+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)