This is the abridged developer documentation for ResponsiveVoice # ResponsiveVoice > TypeScript-first, from the browser to the server. Quickstart ## Speak in a few lines * npm (browser) ```bash npm install @responsivevoice/core ``` ```ts import { getResponsiveVoice } from '@responsivevoice/core'; const rv = await getResponsiveVoice({ apiKey: 'YOUR_API_KEY' }); rv.speak('Hello, world!'); ``` * CDN (browser) ```html ``` * REST (Node.js) ```bash npm install @responsivevoice/api-client ``` ```ts import { ResponsiveVoiceAPIClient } from '@responsivevoice/api-client'; const client = new ResponsiveVoiceAPIClient({ apiKey: process.env.RESPONSIVEVOICE_API_KEY, apiSecret: process.env.RESPONSIVEVOICE_API_SECRET, }); const audio = await client.synthesize({ text: 'Hello world', voice: 'UK English Female', format: 'mp3', }); ``` Packages ## Modular by design ### @responsivevoice/core The heart — and lungs — of ResponsiveVoice: the browser client that speaks text aloud, with a familiar API. ### @responsivevoice/api-client REST API client with retry logic, response validation, and beautiful error handling. ### @responsivevoice/types Shared schemas and types used across the entire ecosystem. Why ResponsiveVoice ## Built for the modern web ### TypeScript First Full TypeScript support with strict types and IntelliSense. ### Universal builds ESM, CJS, and a browser bundle (CDN). Tree-shakeable exports. ### 100+ Voices Access to 100+ voices across 50+ languages. ### Backward Compatible Drop-in replacement for legacy responsivevoice.js. Integrations ## Prefer no code? [Add text-to-speech to WordPressThe official plugin — a floating WebPlayer, Listen buttons, and a Gutenberg block. No code required.Get the plugin](/integrations/wordpress/) *For LLMs and AI agents: [llms.txt](/llms.txt), [llms-full.txt](/llms-full.txt), or the [For AI Agents](/guides/ai-agents/) guide — append `.md` to any docs URL.* # Changelog > Release notes for every published ResponsiveVoice package Each package versions independently, so every one keeps its own release notes. [Core](/changelog/core/)@responsivevoice/core — the browser client. [API Client](/changelog/api-client/)@responsivevoice/api-client — the REST and WebSocket client. [Features](/changelog/features/)@responsivevoice/features — the page-level speech features. [Types](/changelog/types/)@responsivevoice/types — schemas and inferred types. [Text](/changelog/text/)@responsivevoice/text — chunking and duration estimation. # Examples > Learn by example with working demos and code samples Explore working examples that demonstrate ResponsiveVoice capabilities. All examples live in the [examples repository](https://github.com/responsivevoice/examples). ## CDN Examples Drop a ` ``` Without an API key the library runs in **demo mode** — it falls back to browser-native Web Speech API voices. Call `rv.isDemoMode()` at runtime to detect this state (for example, to surface a “Demo Mode” indicator in your UI). ## Next Steps * [Extended Usage Example](/examples/extended) — voice selection, speech controls, event logging * [npm + Vite Example](/examples/vite) — the same flow via ESM import and a bundler * [Events Guide](/guides/events) — the full event lifecycle * [Voice Selection Guide](/guides/voice-selection) — picking voices programmatically # CLI Tool > Command-line text-to-speech utility for Node.js A command-line tool that synthesizes text to speech and saves audio to a file. Uses `@responsivevoice/api-client` directly — server-side only, no browser involvement. Tip [Source code](https://github.com/responsivevoice/examples/tree/main/node/cli) ## Quick start ```bash export RESPONSIVEVOICE_API_KEY="your-api-key" # https://app.responsivevoice.org export RESPONSIVEVOICE_API_SECRET="your-api-secret" # "Server-to-server API secrets" npm install npm run cli -- "Hello, world!" --output hello.mp3 ``` Unlike the browser examples, the CLI has no demo mode — the API client requires a valid key and secret to make HTTP requests to the synthesis endpoint. The `README` in the source tree lists every flag (`--voice`, `--rate`, `--pitch`, `--volume`, `--stdout`, `--list-voices`). ## Next Steps * [HTTP Server](/examples/server) — REST wrapper around the same API client * [REST API](/rest-api/) — direct HTTP usage * [API Client Reference](/api/api-client/src) — full client documentation # Events & Callbacks Example > Live view of every public event and per-call callback firing A focused teacher for the speech lifecycle — speak one utterance and watch every public event and per-call callback fire in real time. Tip [Live demo](https://examples.responsivevoice.org/browser/events-callbacks/) · [Source code](https://github.com/responsivevoice/examples/tree/main/browser/events-callbacks) ## Why a separate example There are two parallel ways to observe speech: `responsiveVoice.addEventListener(name, fn)` (process-wide, persists across calls) and inline `onstart` / `onend` / `onerror` / `onboundary` callbacks passed to `speak()` (bound to a single utterance). Most confusion in the wild stems from mixing them up. This example surfaces both at the same visual level — a row of per-call pills bound to one utterance, and a row of global pills that fire across the lifetime of the page — so the distinction is concrete instead of conceptual. ## What you’ll see * **Two rows of pills.** The top row (four pills: `onstart`, `onboundary`, `onend`, `onerror`) are the per-call callbacks you pass to `speak()`. The bottom row (nine pills: `OnReady`, `OnVoiceResolved`, `OnStart`, `OnPartStart`, `OnPartEnd`, `OnPause`, `OnResume`, `OnEnd`, `OnError`) are the global events you register with `addEventListener()`. * **The default text triggers chunking.** The text box starts with a multi-sentence sample long enough that the engine splits it into several parts. Click Speak and you’ll see `OnPartStart` show `part X of N` and `OnPartEnd` count up as each chunk finishes. * **Pills reset on every Speak click**, except `OnReady` — that one fires once when the page loads and stays green for the rest of the session. * **The full event log sits below the pills.** Once you recognize a pill, you can scroll the log to see the actual data each event carries. * **`onboundary` only fires for browser-native voices.** When a voice plays through the HTTP fallback (server-side audio), the boundary callback stays quiet and its counter doesn’t move. * **`OnPause` has a \~60-second browser limit.** If you click Pause and don’t click Resume within about a minute, the browser cancels the speech automatically and you’ll see `OnEnd` fire instead of `OnResume`. ## Next Steps * [Events Guide](/guides/events) — full event reference and async/await patterns * [Basic Example](/examples/basic) — minimal `OnReady` + `speak()` flow without the inspection scaffolding # Extended Usage Example > Full-featured demo with voice selection, speech controls, and event logging A full-featured example showing voice selection, playback controls, speech parameter tuning, and a live event log. Tip [Live demo](https://examples.responsivevoice.org/browser/extended/) · [Source code](https://github.com/responsivevoice/examples/tree/main/browser/extended) ## What it covers * Voice browser with language filtering * `speak` / `pause` / `resume` / `cancel` controls * Rate / pitch / volume sliders * Platform detection (browser, OS, device type) * Real-time event log * Demo mode indicator driven by `rv.isDemoMode()` * Force-fallback toggle (use HTTP audio instead of Web Speech API) ## Speech parameter ranges | Parameter | Min | Default | Max | Description | | --------- | --- | ------- | --- | ------------ | | `rate` | 0.1 | 1.0 | 2.0 | Speech speed | | `pitch` | 0.1 | 1.0 | 2.0 | Voice pitch | | `volume` | 0.0 | 1.0 | 1.0 | Audio volume | ```js rv.speak('Hello!', 'UK English Female', { rate: 1.2, pitch: 1.0, volume: 0.8, }); ``` ## Force fallback mode Bypass the Web Speech API and always use HTTP audio from the server: ```js await rv.init({ apiKey: 'your-api-key', forceFallback: true }); ``` Useful when you need consistent audio across browsers, or when Web Speech API voices are unavailable on the target device. ## Next Steps * [Basic Example](/examples/basic) — the minimal starting point * [Voice Selection Guide](/guides/voice-selection) — advanced voice filtering * [Events Guide](/guides/events) — full event reference # HTTP Server > REST API server for text-to-speech synthesis A minimal HTTP server that exposes REST endpoints for text-to-speech synthesis. Useful as a reverse proxy — keeps your API key off the browser and lets your frontend call your own origin. Tip [Source code](https://github.com/responsivevoice/examples/tree/main/node/server) ## Quick start ```bash export RESPONSIVEVOICE_API_KEY="your-api-key" # https://app.responsivevoice.org export RESPONSIVEVOICE_API_SECRET="your-api-secret" # "Server-to-server API secrets" npm install npm run server # Server on http://localhost:3001 ``` ## Endpoints | Method | Path | Description | | ------ | ---------------------- | ----------------------------- | | GET | `/` | API documentation | | GET | `/voices` | List all voices | | GET | `/voices/:lang` | Voices by language | | POST | `/synthesize` | Synthesize speech (JSON body) | | GET | `/synthesize?text=...` | Synthesize via query params | ## Calling from a frontend ```js async function speak(text) { const response = await fetch('http://localhost:3001/synthesize', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ text, voice: 'UK English Female' }), }); const audio = new Audio(URL.createObjectURL(await response.blob())); audio.play(); } ``` The server ships with permissive CORS headers (`Access-Control-Allow-Origin: *`). Tighten to your domain(s) before deploying anything public-facing. ## Next Steps * [CLI Tool](/examples/cli) — same API client, command-line form * [REST API](/rest-api/) — direct HTTP usage * [API Client Reference](/api/api-client/src) — full client documentation # npm + Vite Example > Installed from npm, bundled for the browser by Vite `@responsivevoice/core` installed from npm and bundled for the browser by [Vite](https://vitejs.dev/). Same feature set as the [Basic Example](/examples/basic), but the library arrives via ESM `import` instead of a CDN ` ``` The feature ships in the CDN bundle — no extra imports. ## Multi-mount When `selector` matches more than one top-level element, an independent player is mounted on each. Single-active-narrator coordination is implicit: starting playback on one resets every other player to idle. Nested matches (e.g. `
` inside `
`) are filtered automatically — only the outermost match mounts. Publishers who specifically want nested mounts write a more specific selector. ## Imperative mount For SPAs and lazy-loaded sections, call `rv.webPlayer?.mount(selectorOrElement, overrides?)` after the element is in the DOM: ```js fetch('/article/123') .then((r) => r.text()) .then((html) => { document.getElementById('dynamic-area').innerHTML = html; const handle = rv.webPlayer?.mount('#dynamic-area', { theme: 'neutral' }); // later, before removing the element from the DOM: // handle?.unmount(); }); ``` Overrides are leaf-merged over the init config — `{ controls: { brand: false } }` keeps the rest of the init defaults intact. ## Skipping content Any element carrying `data-rv-skip` is excluded from narration: ```html

Read this paragraph.

This block is skipped.

Continue reading from here.

``` ## Next steps * [Web Player Customization](/examples/web-player-customization) — live control panel for every option. # Web Player Customization > Live control panel for every webPlayer option — theme, controls, navigation, layout, and CSS spacing variable. Tune every `webPlayer` option in real time. Each toggle re-mounts the player via `rv.webPlayer.mount()` with leaf-merged overrides, and the matching JSON config snippet updates alongside the player so you can copy-paste the result into your own setup. Tip [Live demo](https://examples.responsivevoice.org/browser/web-player-customization/) · [Source code](https://github.com/responsivevoice/examples/tree/main/browser/web-player-customization) ## What you can tweak * **Theme** — `Neutral`, `ResponsiveVoice`, or custom tokens (9-token color picker covering bg, fg, muted, accent, accentSoft, hover, border, track, fill). * **Controls** — toggle progress, time, skip, speed, brand individually. Play / pause is always shown. * **Navigation** — paragraph highlight and click-to-jump independently. * **Position** — main-pill placement: keyword (`before` / `after` / `inline`) relative to the container, or `{ target, at }` for a custom mount target. * **Layout** — main-pill width (`shrink` / `fill`) and outer display (`block` / `inline`). * **Mini-Player** — visibility, viewport corner (or CSS-offset object), and entrance/exit animation. * **Sanitize** — keep scripts, styles, controls, and media out of narration (on by default), plus extra `exclude` selectors of your own. * **Spacing** — the `--rv-player-margin` CSS custom property, controlling the player’s surrounding margin. ## How re-mount works The example panel keeps a single mount handle and replaces it on every change: ```js let handle = null; function remount() { handle?.unmount(); handle = rv.webPlayer?.mount('#sample', buildConfig()); } ``` `buildConfig()` reads the panel state and returns a `WebPlayerMountOverrides` object that’s leaf-merged over the init config. The same shape works in `rv.init({ features: { webPlayer: ... } })` for static sites. ## Custom mount target The `position` field accepts either a keyword (`'before'`, `'after'`, `'inline'`) relative to `selector`, or an object that mounts the player into any element on the page. This is useful when the article element is constrained by your layout (sidebar, fixed slot, CMS-driven composition): ```ts rv.webPlayer?.mount('#article', { position: { target: '#player-slot', at: 'inside' }, }); ``` `at` accepts `'inside'` (first child of the target, the default), `'before'` (sibling before), or `'after'` (sibling after). `target` is required — the keyword form covers the article-relative case. If `target` doesn’t match anything in the DOM at mount time, the player falls back to the keyword `'before'` and logs a warning. ## Mini-player position The floating mini-player surfaces when the main player scrolls out of view. Its viewport corner is configurable via `miniPlayer.position`: ```ts rv.webPlayer?.mount('#article', { miniPlayer: { enabled: true, position: 'bottom-right' }, }); ``` Accepted values: * A corner keyword: `'top-left'`, `'top-right'`, `'bottom-left'`, `'bottom-right'`. Default `'bottom-left'`. * A CSS-offset object: `{ top, right, bottom, left }`, each a CSS length string. At least one side is required; opposing sides (`top` + `bottom`, `left` + `right`) are rejected. ```ts rv.webPlayer?.mount('#article', { miniPlayer: { enabled: true, position: { top: '80px', right: '20px' } }, }); ``` The boolean shorthand is the natural form for the on/off case — `miniPlayer: true` enables the mini-player at the default corner, `miniPlayer: false` disables it. ## Mini-player animation `miniPlayer.animation` controls how the mini-player enters and leaves as you scroll: ```ts rv.webPlayer?.mount('#article', { miniPlayer: { enabled: true, animation: 'fade' }, }); ``` Accepted values: * `'slide'` — fades in and slides from the docked corner. Default. * `'fade'` — fades in and out, no movement. * `'pop'` — fades in with a brief scale-up. * `'none'` — appears and disappears instantly. The slide direction follows where the mini-player sits: anything in the lower half of the viewport rises into place, anything in the upper half drops in — so corner keywords and custom offsets both enter from the nearest edge. Motion is skipped automatically when the browser requests reduced motion, so every preset falls back to an instant swap for those readers. ## Choosing a voice per player By default every web player narrates with the website’s default voice (the `voice` profile from `/v2/config`). The web-player config exposes four flat playback fields — `voice`, `rate`, `pitch`, `volume` — that mirror the arguments of `core.speak(text, voice, params)`. Each is independently optional and overrides the website default for one player: ```ts rv.webPlayer?.mount('#article', { voice: 'US English Male', rate: 0.9, }); ``` `voice` accepts the full [`VoiceSelector`](/guides/voice-selection/) grammar. In JS, write whichever form is most natural — strings for named voices, real `RegExp` literals for patterns, plain objects for structured queries. The schema normalizes a `RegExp` to its JSON-clean `{ regex, flags }` form on parse, so the same selector works identically in JS, server config, and every SDK language: ```ts // 1. Exact voice name rv.webPlayer?.mount('#en-article', { voice: 'UK English Female', }); // 2. Structured query — pick a Portuguese female voice from any provider rv.webPlayer?.mount('#pt-article', { voice: { lang: 'pt', gender: 'female' }, }); // 3. Regex pattern — first voice whose name matches rv.webPlayer?.mount('#multi-article', { voice: /English.*Male/i, }); ``` The shape parallels `speak()` directly: ```ts // Direct speak() call: rv.speak('Hello', /UK English/, { rate: 0.9, volume: 0.8 }); // ^text ^voice ^speech params // Equivalent web-player config — same fields, same names: rv.webPlayer?.mount('#article', { voice: /UK English/, rate: 0.9, volume: 0.8, }); ``` ### Setting a default for every player The same fields are valid in the website config (`webPlayer.voice` / `rate` / `pitch` / `volume`), in which case every auto-discovered article starts with those values instead of the website-wide default: ```ts rv.init({ features: { webPlayer: { enabled: true, voice: 'US English Male', rate: 0.95, }, }, }); ``` Per-mount overrides leaf-merge over this config, so a player that mounts with `{ rate: 1.2 }` keeps the `'US English Male'` voice and only changes the rate. ### What inherits, what overrides Each playback field is independently optional. Any field you omit falls through to the website default voice profile (the profile’s `name` becomes the string-form `voice` selector when none is set): | Field | When omitted | | -------- | --------------------------------------------------------- | | `voice` | Inherits the website default voice’s `name` as a selector | | `pitch` | Inherits the website default `pitch` | | `rate` | Inherits the website default `rate` | | `volume` | Inherits the website default `volume` | ## Next steps * [Web Player Example](/examples/web-player) — the basic integration without the customization panel. * [Voice Selection](/guides/voice-selection/) — the full `VoiceSelector` grammar used by the `voice` field. # Getting Started > Install ResponsiveVoice, speak your first sentence, or upgrade from v1 Three ways in, depending on where you are starting from. [Quick Start](/getting-started/quick-start/)New here? Speak your first sentence in a few lines, from a script tag or from npm. [Installation](/getting-started/installation/)Every install route in detail — CDN bundle, npm, and which package each project needs. [Migration Guide](/getting-started/migration/)Already using the legacy responsivevoice.js? Upgrade to @responsivevoice/core, with the API differences called out. ## Next [Guides](/guides/)Voice selection, the web player, events, and the rest of the feature surface. [Examples](/examples/)Runnable projects — browser, Vite, CLI, and server. # Installation > How to install ResponsiveVoice packages ## Core package The main client library for browser text-to-speech. Both paths deliver the same package — pick the one that fits your project: * **Browser bundle (CDN)** — no build step; add one script tag and use the global `responsiveVoice`. Best for plain HTML, prototypes, CMS sites, and legacy pages. Call `init()` with your API key — without it you get demo mode (browser default voice only). * **npm** — for apps with a bundler (Vite, webpack, Next). Install and import. - Browser bundle (CDN) ```html ``` - npm ```bash npm install @responsivevoice/core # or: pnpm add @responsivevoice/core # or: yarn add @responsivevoice/core ``` ```typescript import { getResponsiveVoice } from '@responsivevoice/core'; const rv = await getResponsiveVoice({ apiKey: 'YOUR_API_KEY' }); ``` ## API client REST API client for direct server communication. * npm ```bash npm install @responsivevoice/api-client ``` * pnpm ```bash pnpm add @responsivevoice/api-client ``` * yarn ```bash yarn add @responsivevoice/api-client ``` ## Types package `@responsivevoice/types` (TypeScript types + Zod schemas) ships automatically as a dependency of the packages above, and `api-client` re-exports the common ones — so you rarely install it directly. If you want the schemas standalone: `npm install @responsivevoice/types`. ## Requirements ### Browsers The browser bundle (CDN) is Babel + core-js polyfilled and works in Chrome 69+, Firefox 65+, Safari 12+, Edge 79+, iOS Safari 12+, Chrome Android 69+, Android WebView 69+ without any additional setup. ESM/CJS packages are syntax-compatible but leave browser polyfilling to your bundler; see [Browser Support](/guides/browser-support/) for each package’s minimum browser versions. ### Node.js | Package | Minimum | Full functionality | | ----------------------------- | ------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `@responsivevoice/types` | 14+ | All features | | `@responsivevoice/text` | 14+ | All features | | `@responsivevoice/api-client` | 16+ | HTTP synthesis requires Node 18+ for native `fetch` and `Blob`. On Node 16–17, pass a `fetch` implementation via the `fetch` config option (e.g. `node-fetch` or `undici`). | | `@responsivevoice/core` | 16+ | Browser-only (Web Speech API + audio playback); Node is the build environment (bundle with Vite/webpack), not a runtime target | | `@responsivevoice/features` | 14+ | Browser-only (DOM APIs) | WebSocket streaming requires Node 22+ for the native `WebSocket` global, or pass any W3C-compatible WebSocket implementation (e.g. the `ws` package) via the `WebSocket` config option. ### TypeScript 5.0+ (optional, but recommended). Full type definitions included in all packages. # Migration Guide > Upgrade from legacy responsivevoice.js to @responsivevoice/core This guide helps you migrate from the legacy `responsivevoice.js` script to the modern `@responsivevoice/core` package. ## What’s New The new package provides: * **TypeScript support** with full type definitions * **Modern build formats** (ESM, CJS, browser bundle) * **Tree-shakeable exports** * **Better error handling** * **API-first architecture** (voice data fetched at runtime) * **Full backward compatibility** with the legacy API * **Access to Premium neural voices** from several specialized TTS Providers (via bring-your-own-keys) * **New UI features** — see the new [ResponsiveVoice WebPlayer](/guides/web-player/) in action in the [live example](https://examples.responsivevoice.org/browser/web-player/) and [how to customize it](https://examples.responsivevoice.org/browser/web-player-customization/). Tip The migration is designed to be a drop-in replacement. Most code will work without changes. ## Installation Changes ### Before ```html ``` ### After The closest migration keeps a script tag — load the browser bundle from the CDN and move your key into an `init()` call. Use npm instead if your app has a bundler. * Browser bundle (CDN) ```html ``` * npm ```bash npm install @responsivevoice/core ``` ```typescript import { getResponsiveVoice } from '@responsivevoice/core'; const rv = await getResponsiveVoice({ apiKey: 'YOUR_KEY' }); ``` ## API Mapping The core API remains the same: | Legacy | Modern | Notes | | ----------------------------- | ---------------- | -------------- | | `responsiveVoice.speak()` | `rv.speak()` | Same signature | | `responsiveVoice.cancel()` | `rv.cancel()` | Same behavior | | `responsiveVoice.pause()` | `rv.pause()` | Same behavior | | `responsiveVoice.resume()` | `rv.resume()` | Same behavior | | `responsiveVoice.getVoices()` | `rv.getVoices()` | Same behavior | | `responsiveVoice.isPlaying()` | `rv.isPlaying()` | Same behavior | In the browser bundle the global stays `responsiveVoice`; with npm you call the same methods on the `rv` instance from `getResponsiveVoice()`. The signatures are identical either way. ## Configuration Changes ### Before ```javascript // Global configuration via URL parameter ; // Or via global variable window.responsiveVoice.setDefaultVoice('UK English Female'); ``` ### After * Browser bundle (CDN) ```html ``` * npm ```typescript import { getResponsiveVoice } from '@responsivevoice/core'; const rv = await getResponsiveVoice({ apiKey: 'YOUR_KEY', defaultVoice: 'UK English Female', }); ``` ## Event System Per-call event callbacks are unchanged from the legacy script: * Browser bundle (CDN) ```javascript responsiveVoice.speak('Hello', 'UK English Female', { onstart: () => console.log('Started'), onend: () => console.log('Ended'), }); ``` * npm ```typescript rv.speak('Hello', 'UK English Female', { onstart: () => console.log('Started'), onend: () => console.log('Ended'), }); ``` ## Breaking Changes Caution The following changes may require code updates: ### 1. Voice Data is Fetched at Runtime Voice mappings are no longer bundled in the JavaScript file — `init()` fetches them from the API and caches them locally. **Impact:** First initialization needs a network connection to fetch the voice catalog. There’s nothing to pre-fetch — `init()` already loads voices before it resolves. ### 2. No Internet Explorer Support The browser bundle (CDN) ships the polyfills modern browsers need and targets Chrome 69+, Firefox 65+, Safari 12+, Edge 79+, iOS Safari 12+, Chrome Android 69+, Android WebView 69+. Internet Explorer 11 is not supported. **Impact:** If you need to support IE11 or similarly old browsers, stay on the legacy `responsivevoice.js` (v1). ## TypeScript Support The new package includes full TypeScript definitions: ```typescript import { getResponsiveVoice, type ResponsiveVoiceInitOptions, } from '@responsivevoice/core'; import type { SpeakParams } from '@responsivevoice/types'; const options: ResponsiveVoiceInitOptions = { apiKey: 'YOUR_KEY', }; const rv = await getResponsiveVoice(options); const params: SpeakParams = { pitch: 1.0, rate: 1.0, onend: () => console.log('Done'), }; rv.speak('Hello', 'UK English Female', params); ``` ## Troubleshooting ### ”Voice not found” Error Ensure the voice name exactly matches an available voice: ```typescript const voices = rv.getVoices(); console.log(voices.map((v) => v.name)); ``` ### “API key required” Error Make sure to initialize with an API key: ```typescript const rv = await getResponsiveVoice({ apiKey: 'YOUR_KEY' }); ``` ### Audio Not Playing on iOS Mobile browsers (iOS, and Android Chrome) block autoplay, but ResponsiveVoice handles this automatically with a built-in permission prompt that captures the first tap and unlocks audio. If you’ve disabled the prompt, trigger speech from your own gesture handler instead: ```typescript button.addEventListener('click', () => { rv.speak('Hello'); }); ``` See the [FAQ](/guides/faq/#does-responsivevoice-work-on-ios) for details. ## Need Help? * [API Reference](/api/core/src/) * [Support](https://responsivevoice.org/support) # Quick Start > Get up and running with ResponsiveVoice in minutes ## Basic Usage Drop in the script tag or install from npm — both reach the same API: * Browser bundle (CDN) ```html ``` * npm ```bash npm install @responsivevoice/core # or: pnpm add @responsivevoice/core / yarn add @responsivevoice/core ``` ```typescript import { getResponsiveVoice } from '@responsivevoice/core'; const rv = await getResponsiveVoice({ apiKey: 'YOUR_API_KEY' }); rv.speak('Hello, world!'); ``` Note The examples below use the `rv` instance returned by `getResponsiveVoice()` (npm). With the browser bundle, call the same methods on the global `responsiveVoice`. ## Voice Selection ```typescript // Get available voices const voices = rv.getVoices(); console.log(voices); // Speak with a specific voice rv.speak('Hello', 'UK English Female'); rv.speak('Hello', 'US English Male'); rv.speak('Hallo', 'Deutsch Female'); ``` ## Playback Control ```typescript // Pause speech rv.pause(); // Resume speech rv.resume(); // Cancel speech rv.cancel(); // Check if speaking if (rv.isPlaying()) { console.log('Currently speaking'); } ``` ## Events ```typescript // Per-call callbacks — bound to this speak() call rv.speak('Hello with events', 'UK English Female', { onstart: () => console.log('Speech started'), onend: () => console.log('Speech ended'), onerror: (error) => console.error('Speech error:', error), onboundary: (charIndex, name) => console.log('Word boundary:', charIndex, name), }); // Global events — fire across every speak() call (pause/resume live here) rv.addEventListener('OnPause', () => console.log('Speech paused')); rv.addEventListener('OnResume', () => console.log('Speech resumed')); ``` Tip For more advanced usage, see the [Voice Selection](/guides/voice-selection/) and [Events](/guides/events/) guides. # Guides > Task-focused guides for choosing voices, configuring features, and shipping ResponsiveVoice Task-focused guides. If you are setting up for the first time, start with [Quick Start](/getting-started/quick-start/) instead. ## Deciding [Why ResponsiveVoice?](/guides/why-responsivevoice/)How v2 compares to ElevenLabs, Azure, and Google Cloud for adding speech to a website. [FAQ](/guides/faq/)Pricing, API keys, streaming, browser and Node.js support, available voices. [Browser Support](/guides/browser-support/)Minimum browser and Node.js versions, per package. ## Choosing a voice [Voice Selection](/guides/voice-selection/)Select voices by name, regex, or structured query. [Voice Resolver Hook](/guides/voice-resolver/)Intercept and rewrite voice selectors before they resolve. ## Speaking [Web Player](/guides/web-player/)A drop-in article reader: play controls, paragraph highlighting, mini-player. [App Features](/guides/app-features/)No-code voice features configured from the dashboard, no JavaScript required. [Events](/guides/events/)React to speech starting, ending, pausing, and failing. [Text Chunking](/guides/text-chunking/)How long text is split for synthesis, and when to tune it. ## Machine-readable docs [For AI Agents](/guides/ai-agents/)llms.txt, text bundles, and per-page Markdown for LLMs and coding agents. # For AI Agents > Machine-readable documentation for LLMs and AI agents — llms.txt, full/abridged text bundles, and per-page Markdown. This documentation ships a machine-readable layer for LLMs and AI agents. Every page is available as plain text and Markdown, with curated bundles for whole-site ingestion. ## Agent Skill Teach your coding agent to install and run ResponsiveVoice with one command: ```bash npx skills add responsivevoice/skills ``` It installs into Claude Code, Cursor, GitHub Copilot, Gemini CLI, and other agents that read filesystem [Agent Skills](https://github.com/responsivevoice/skills), covering the browser library, REST API, and language SDKs. ## Endpoints Fetching one topic? Use a section bundle or `/llms-small.txt`. Reserve `/llms-full.txt` for whole-corpus ingestion — it is large and ends with the full generated API reference. [/llms.txt](/llms.txt)Index of the documentation sets, following the llmstxt.org convention. Start here. [Section bundles](/_llms-txt/getting-started.txt)One topic per file: getting-started, guides, sdks, examples, rest-api at /\_llms-txt/.txt. Smallest fetch for a single topic. [/llms-small.txt](/llms-small.txt)Abridged build, API reference removed. Best for conceptual queries on a budget. [/llms-full.txt](/llms-full.txt)The complete documentation as a single text file, including the generated API reference. Large. [/llms.md](/llms.md)A Markdown index of every page. ## Per-page Markdown Append `.md` to any documentation URL to get its Markdown source — for example, `/getting-started/installation.md`. ## Discovery You don’t need to know these URLs in advance — agents and crawlers can find them automatically: * **Every page links to them.** Each page’s `` includes `` tags pointing to `/llms.txt` and `/llms.md`, so fetching any page reveals the machine-readable versions. * **`/llms.txt` sits at the site root**, following the [llmstxt.org](https://llmstxt.org) convention — the standard place agents look first. * **`robots.txt` allows crawling** and lists the sitemap (`/sitemap-index.xml`) and the `llms.*` files. # App Features > Configure the no-code website voice features available from the ResponsiveVoice app. The [ResponsiveVoice app](https://app.responsivevoice.org) lets you turn website voice features on and off without writing custom JavaScript. These features use the same v2 configuration that powers the SDK, so changes made in the app are reflected in the website configuration returned to your installed ResponsiveVoice script. ![The ResponsiveVoice app's website configuration page: site details at the top and the full list of voice-feature toggles below.](/img/app/dashboard.png) [Open the ResponsiveVoice app](https://app.responsivevoice.org)Configure these features for your site — no code required. Note Some features speak on their own — a few, like the welcome message, as soon as the page loads. Browsers don’t allow audio until the visitor interacts with the page, so these play once the visitor responds to ResponsiveVoice’s built-in permission prompt, which captures that first interaction automatically. ## Quick Reference | Feature | What it does | | ----------------------------------------------------- | -------------------------------------------------------------------- | | [Welcome message](#welcome-message) | Speaks a short message after the page loads. | | [Speak selected text](#speak-selected-text) | Speaks text the visitor highlights. | | [Speak links](#speak-links) | Speaks link text when the visitor hovers over a link. | | [Accessibility navigation](#accessibility-navigation) | Speaks interactive elements as the visitor tabs through the page. | | [Paragraph navigation](#paragraph-navigation) | Lets visitors move through readable text with Ctrl+Up and Ctrl+Down. | | [Inactivity message](#inactivity-message) | Speaks after a period without visitor interaction. | | [End-of-page message](#end-of-page-message) | Speaks when the visitor reaches the bottom of the page. | | [Exit-intent message](#exit-intent-message) | Speaks when the visitor moves toward leaving the page. | | [Multiple messages](#multiple-messages) | Rotates between several message variants. | | [Web player](#web-player) | Adds an article reader with highlighting and a mini-player. | ## Welcome Message The welcome message speaks a short greeting or instruction shortly after a visitor opens the page. Use it for a brief introduction, not for long announcements. A good message tells visitors what they can do next, for example: “Welcome. Select any text to hear it spoken aloud.” Configuration notes: * Enable **Welcome message** in the app. * Enter the message text. * Use **Play welcome message once per session** when repeat playback would be annoying. * Preview the message before saving. Troubleshooting: * The message plays once the visitor responds to the permission prompt — browsers don’t allow audio before that first interaction, so it won’t speak the instant the page loads. * Keep the text short so it does not overlap with other automatic messages. ## Speak Selected Text Speak selected text lets visitors highlight text on the page and hear it spoken aloud. This is useful for readers who want help with a sentence, word, product description, or article section without playing the whole page. Configuration notes: * Enable **Speak selected text** in the app. * Save the configuration and reload the website page. * Test by selecting a short piece of visible text. Troubleshooting: * Text inside buttons, forms, scripts, or hidden elements may not be suitable for selection playback. * If a page prevents text selection with CSS or custom JavaScript, this feature may not trigger. ## Speak Links Speak links reads the visible text of a link when a visitor hovers over it. This helps visitors understand where a link goes before activating it. It works best when links have meaningful labels such as “View pricing” rather than vague labels such as “Click here.” Configuration notes: * Enable **Speak links** in the app. * Save and reload the website page. * Hover over a normal text link to test it. Troubleshooting: * Links with no visible text or only decorative icons may not produce useful speech. * For keyboard-first navigation, use [Accessibility navigation](#accessibility-navigation). ## Accessibility Navigation Accessibility navigation speaks interactive elements as visitors move through the page with the Tab key. It is intended for links, buttons, and form controls. The spoken text comes from the element label, accessible name, or visible text. Configuration notes: * Enable **Speak interactive elements using the Tab key** in the app. * Save and reload the website page. * Press Tab through the page and listen to each focused element. Troubleshooting: * If an element has no useful label, improve its visible text, `aria-label`, or associated form label. * Avoid enabling too many automatic speech features on the same page until you have tested the experience. ## Paragraph Navigation Paragraph navigation lets visitors move through readable page content with Ctrl+Up and Ctrl+Down while hearing the current paragraph. This is useful for long-form pages, help articles, and documentation where visitors may want keyboard control over reading. Configuration notes: * Enable **Speak paragraph using CTRL-UP and CTRL-DOWN keys** in the app. * Save and reload the website page. * Test on a content-heavy page with normal paragraphs and headings. Troubleshooting: * Short pages may not have enough readable blocks for this to feel useful. * Pages with unusual markup may need the [Web Player advanced content settings](/guides/web-player/#advanced) instead. ## Inactivity Message The inactivity message speaks after the visitor has not interacted with the page for a period of time. Use it carefully. The best messages are short and helpful, such as “Still there? You can press play to listen to this page.” Configuration notes: * Enable **Inactivity message** in the app. * Enter the message text. * Preview the message before saving. Troubleshooting: * Do not combine long inactivity, welcome, end-of-page, and exit-intent messages without testing the page flow. * If visitors report unexpected audio, shorten the message or disable this feature on quieter pages. ## End-of-Page Message The end-of-page message speaks when the visitor reaches the bottom of the page. Use it as a closing prompt, such as a support reminder, next step, or short call to action. Configuration notes: * Enable **End-of-page message** in the app. * Enter the message text. * Scroll to the bottom of a test page after saving. Troubleshooting: * Infinite-scroll pages or pages with dynamically loaded content may need extra testing. * Keep the message short enough that it does not interrupt normal navigation. ## Exit-Intent Message The exit-intent message speaks when the visitor moves the pointer toward leaving the page. Use it sparingly. It can be helpful for short reminders, but it can also surprise visitors if it is too long or too frequent. Configuration notes: * Enable **Exit-intent message** in the app. * Enter a short message. * Test by moving the pointer toward the top of the browser window. Troubleshooting: * Exit intent is pointer-based and may not apply on all touch devices. * Avoid using this feature for essential information. ## Multiple Messages Message fields can contain several alternatives separated by a pipe character (`|`). ResponsiveVoice chooses one at random when the feature speaks. Example: ```text Welcome to our site.|Need help? Select any text to hear it spoken.|You can listen to this page while you browse. ``` This works for message-style features such as welcome, inactivity, end-of-page, and exit-intent messages. Tips: * Keep each variant short. * Do not put a pipe character inside the message itself. * Preview several times when testing random messages. ## Web Player The web player adds a visible article reader with paragraph highlighting, click-to-jump, and playback controls. This is what your visitors see: ![The web player's pill control: a play button, speed, skip buttons, a progress bar with elapsed and total time, and the ResponsiveVoice brand icon.](/img/web-player/player-controls.png) When the main control scrolls out of view, a floating mini-player keeps playback within reach: ![The floating mini-player docked in the bottom-left corner, with a circular progress ring around the play button.](/img/web-player/mini-player.png) You configure all of this from the **Web Player** panel in the app — no code required. Each section below maps an app setting to what it does. Developers installing or overriding the player in code should use the [Web Player guide](/guides/web-player/) instead. ### Theme Choose a preset — **Neutral** or **ResponsiveVoice** — or set custom colors to match your brand. ![The Theme section of the app's Web Player panel.](/img/web-player/settings-theme.png) ### Layout ![The Layout section of the app's Web Player panel: Position, Width, and Display.](/img/web-player/settings-layout.png) | App field | Default | What it does | | --------- | ------------------------- | ------------------------------------------------------------------------ | | Position | *Before the content* | Before, after, or inline with the content, or inside a custom slot. | | Width | *Shrink to content* | Shrink hugs the controls; Fill spans the container. | | Display | *Block (on its own line)* | Block gives the player its own line; Inline flows with surrounding text. | ### Controls ![The Controls section of the app's Web Player panel: show/hide checkboxes for each control, plus the floating mini-player options.](/img/web-player/settings-controls.png) | App field | Default | What it does | | --------------------- | ------------- | ------------------------------------------------------------- | | Progress | *On* | Shows progress through the readable content. | | Time | *On* | Shows elapsed and total estimated time. | | Skip | *On* | Shows previous and next paragraph buttons. | | Speed | *On* | Shows the speed cycle button. | | Brand | *On* | Shows the ResponsiveVoice brand icon. | | Floating mini-player | *On* | Shows the mini-player when the main player is out of view. | | Mini-player position | *Bottom left* | Places the mini-player in a viewport corner or custom offset. | | Mini-player animation | *Slide* | Uses Slide, Fade, Pop, or None. | ### Behavior ![The Behavior section of the app's Web Player panel: toggles for Highlight paragraphs, Click to jump, and Skip code & hidden content.](/img/web-player/settings-behavior.png) | App field | Default | What it does | | -------------------------- | ------- | ------------------------------------------------------------------------------------------- | | Highlight paragraphs | *On* | Highlights the currently spoken element. | | Click to jump | *On* | Lets visitors click a paragraph to start reading there. | | Skip code & hidden content | *On* | Excludes scripts, styles, form controls, embedded media, and hidden content from narration. | ### Advanced ![The Advanced section of the app's Web Player panel: Content container, Paragraphs to read, Voice override, and Exclude from narration.](/img/web-player/settings-advanced.png) | App field | Default | What it does | | ---------------------- | ----------------- | -------------------------------------------------------------------- | | Content container | *article* | CSS selector for the element that contains readable content. | | Paragraphs to read | *p, h2, h3, li* | CSS selector for the readable elements inside the container. | | Voice override | *Website default* | Optional voice for this player only. | | Exclude from narration | *empty* | Extra CSS selectors to skip, in addition to the built-in exclusions. | ### Troubleshooting #### The player doesn’t appear Confirm the **Web Player** toggle is on and your ResponsiveVoice script is installed on the page. If you set a **Content container**, make sure it matches an element that exists on the page. #### The player is in the wrong place Adjust **Position** under Layout. If your theme wraps the whole page in one article, point **Content container** at the specific content area instead. #### The wrong text is read, or paragraphs are missing Check that **Paragraphs to read** matches your content, and that nothing you want read is being removed by **Skip code & hidden content** or **Exclude from narration**. #### The mini-player doesn’t appear Confirm **Floating mini-player** is on, then scroll until the main player leaves view while it is playing. ### Live preview and testing Use the app preview to confirm the experience before saving, then test once on the real site too: * Press play and confirm the selected voice sounds right. * Confirm the progress, time, skip, speed, and brand controls match the Controls panel. * Click a paragraph if **Click to jump** is enabled. * Confirm highlighting appears if **Highlight paragraphs** is enabled. * Scroll until the main player leaves view and confirm the mini-player appears if enabled. * Add a known excluded element and confirm it is skipped. ### Advanced Usage Need to install, theme, or override the player in code? The developer guide covers the programmatic path with `init()` and `mount()`. [Web Player guide (for developers)](/guides/web-player/)Configure and override the web player programmatically with init() and mount(). # Browser Support > Browser and platform compatibility for ResponsiveVoice ResponsiveVoice targets modern evergreen browsers (released \~2020 onward), with automatic fallback. Base text-to-speech needs the Web Speech API or a `fetch`-capable runtime; some features require newer browsers — see [Feature requirements](#feature-requirements). Support is shown per delivery method. The **browser bundle (CDN)** is Babel + core-js polyfilled, so it reaches the widest range; **`@responsivevoice/core`** and **`@responsivevoice/api-client`** are raw npm packages whose minimum versions reflect the browser APIs they use — your bundler supplies any transpilation. [Browser bundle (CDN)](/getting-started/quick-start/) * ![Chrome](/_astro/chrome.CRByiUFQ_Z2pJKxj.svg)Chrome69+ * ![Firefox](/_astro/firefox.1bWoP6pv_Zks0Xz.svg)Firefox65+ * ![Safari](/_astro/safari.na3_-uQk_Z1PHqby.svg)Safari12+ * ![Edge](/_astro/edge.HDH_c98u_ZkUqCK.svg)Edge79+ * ![iOS Safari](/_astro/safari.na3_-uQk_Z1PHqby.svg)iOS Safari12+ * ![Chrome Android](/_astro/chrome.CRByiUFQ_Z2pJKxj.svg)Chrome Android69+ * ![Android WebView](/_astro/android-webview_64x64._FpYT5CQ_1iPoo.webp)Android WebView69+ Polyfilled via Babel + core-js — the widest supported tier. [@responsivevoice/core](/getting-started/installation/#core-package) * ![Chrome](/_astro/chrome.CRByiUFQ_Z2pJKxj.svg)Chrome69+ * ![Firefox](/_astro/firefox.1bWoP6pv_Zks0Xz.svg)Firefox65+ * ![Safari](/_astro/safari.na3_-uQk_Z1PHqby.svg)Safari14+ * ![Edge](/_astro/edge.HDH_c98u_ZkUqCK.svg)Edge79+ * ![iOS Safari](/_astro/safari.na3_-uQk_Z1PHqby.svg)iOS Safari14+ * ![Chrome Android](/_astro/chrome.CRByiUFQ_Z2pJKxj.svg)Chrome Android69+ * ![Android WebView](/_astro/android-webview_64x64._FpYT5CQ_1iPoo.webp)Android WebView69+ [@responsivevoice/api-client](/sdks/typescript/) * ![Chrome](/_astro/chrome.CRByiUFQ_Z2pJKxj.svg)Chrome66+ * ![Firefox](/_astro/firefox.1bWoP6pv_Zks0Xz.svg)Firefox65+ * ![Safari](/_astro/safari.na3_-uQk_Z1PHqby.svg)Safari12+ * ![Edge](/_astro/edge.HDH_c98u_ZkUqCK.svg)Edge79+ * ![iOS Safari](/_astro/safari.na3_-uQk_Z1PHqby.svg)iOS Safari12+ * ![Chrome Android](/_astro/chrome.CRByiUFQ_Z2pJKxj.svg)Chrome Android66+ * ![Android WebView](/_astro/android-webview_64x64._FpYT5CQ_1iPoo.webp)Android WebView66+ *Minimum versions are generated from the APIs each package actually uses ([MDN Browser Compat Data](https://github.com/mdn/browser-compat-data)). npm packages are raw and unpolyfilled; the bundle's reflect its Babel + core-js polyfills.* Note These npm minimum versions are raw and unpolyfilled. To support older browsers, load the CDN bundle (already polyfilled) or add polyfills in your build — see [Polyfilling for older browsers](#polyfilling-for-older-browsers). Tip `@responsivevoice/types` is pure TypeScript with no browser runtime requirements. ## Feature requirements Some capabilities need newer browsers than the base minimum. All are degradable — when the API is missing, ResponsiveVoice falls back automatically. | Feature | Requires | Minimum | | -------------------------- | ----------------- | -------------------------------------------------- | | HTTP/WebSocket streaming | MediaSource | iOS Safari 13+ | | Web player (in-page UI) | Shadow DOM | Firefox 63+, Edge 79+ | | Native (Web Speech) voices | `speechSynthesis` | unavailable in Android WebView (falls back to API) | Note Firefox has limited native voice support. ResponsiveVoice automatically uses the fallback API when needed. ## Polyfilling for older browsers The npm packages ship modern JavaScript and leave transpilation to your bundler — the common convention. To support older browsers, add [core-js](https://github.com/zloirock/core-js) through [@babel/preset-env](https://babeljs.io/docs/babel-preset-env) with a browserslist target: babel.config.js ```js module.exports = { presets: [['@babel/preset-env', { useBuiltIns: 'usage', corejs: '3' }]], }; ``` This polyfills JavaScript built-ins like `URLSearchParams` and `Promise`. DOM and platform APIs (Shadow DOM, MediaSource) can’t be polyfilled — those features need their listed browsers, but base text-to-speech still works without them. ## How Fallback Works ![Diagram](/d2/docs/guides/browser-support-0.svg) ## Native vs Fallback | Feature | Native (Web Speech API) | Fallback (API) | | --------------- | ----------------------- | -------------- | | Latency | Instant | \~100–300 ms\* | | Offline | | | | Voice quality | Varies by OS | Consistent | | Boundary events | | | | SSML support | | | Note \* Fallback synthesis typically returns audio in \~100–300 ms; cached phrases in under 100 ms. Longer or uncached requests may take up to \~700 ms. These figures are indicative, not a guarantee. ## Platform-Specific Notes ### Chrome / Chromium * Best voice selection with Google TTS voices * Voices like “Google UK English Female” available * Full boundary event support * May require user interaction to start audio ### Safari / iOS * Apple voices with “(Enhanced)” variants * Good offline voice quality * iOS requires user gesture to begin playback * Some voices locked to specific iOS versions ### Firefox * Limited native voice support * Falls back to API more frequently * No boundary events when using fallback * Full functionality with API fallback ### Edge * Windows voices available (David, Zira, etc.) * Good Web Speech API support * Microsoft neural voices on Windows 10+ ## Mobile Considerations ### iOS ```typescript // Optional: trigger speech from your own button instead of the built-in prompt document.getElementById('speakBtn').addEventListener('click', () => { rv.speak('Hello from iOS', 'UK English Female'); }); ``` Tip Mobile browsers (iOS, and Android Chrome) block autoplay, so audio needs a user gesture — but ResponsiveVoice handles this automatically with a built-in permission prompt that captures the first tap and unlocks audio. You can disable it and drive speech from your own UI (like the button above). See [the FAQ](/guides/faq/#does-responsivevoice-work-on-ios). ### Android * Android Chrome requires a user gesture before audio — handled automatically by the same built-in permission prompt as iOS * Voice availability varies by device manufacturer * Google TTS voices commonly available * May need to download voices in system settings ### Android WebView Caution The Web Speech API (native TTS) is **not supported** in Android WebView. ResponsiveVoice automatically falls back to the API for all speech synthesis. * Minimum supported: Android WebView 66+ * Native TTS unavailable - always uses API fallback * Audio playback works normally via Audio element * Common in hybrid apps (Cordova, Capacitor, React Native WebView) ## Node.js Support ResponsiveVoice works in Node.js environments using the [`@responsivevoice/api-client`](/getting-started/installation/#api-client) package. The minimum Node.js version varies by package: | Package | Minimum Node.js | | -------------------------------- | --------------- | | `@responsivevoice/types`, `text` | 14+ | | `@responsivevoice/api-client` | 16+ | `@responsivevoice/core` and `@responsivevoice/features` target the browser — Node is only their build environment (bundle with Vite/webpack), not a runtime. ### Node.js 18+ (recommended) All features work out of the box with native `fetch`, `Blob`, and `AbortController`: ```typescript import { ResponsiveVoiceAPIClient } from '@responsivevoice/api-client'; const client = new ResponsiveVoiceAPIClient({ apiKey: process.env.RESPONSIVEVOICE_API_KEY, apiSecret: process.env.RESPONSIVEVOICE_API_SECRET, }); const audio = await client.synthesize({ text: 'Hello from Node.js', voice: 'UK English Female', }); ``` ### Node.js 16–17 Native `fetch` is not available. Pass a fetch implementation via the `fetch` config option: ```typescript import fetch from 'node-fetch'; import { ResponsiveVoiceAPIClient } from '@responsivevoice/api-client'; const client = new ResponsiveVoiceAPIClient({ apiKey: process.env.RESPONSIVEVOICE_API_KEY, apiSecret: process.env.RESPONSIVEVOICE_API_SECRET, fetch, }); ``` ### WebSocket streaming on Node.js < 22 The global `WebSocket` was added in Node.js 22. On older versions, pass a WebSocket implementation: ```typescript import WebSocket from 'ws'; import { WebSocketConnection } from '@responsivevoice/api-client'; const ws = new WebSocketConnection({ baseUrl: 'https://texttospeech.responsivevoice.org', apiKey: process.env.RESPONSIVEVOICE_API_KEY, WebSocket, }); ``` ## Feature Detection Check platform capabilities: ```typescript // Check if the Web Speech API is available const hasNativeTTS = 'speechSynthesis' in window; // Whether ResponsiveVoice can use native voices on this platform const nativeSupported = rv.isNativeSupported(); // Check if a voice name is in the resolvable catalog const voices = rv.getVoices(); const hasUKFemale = voices.some((v) => v.name === 'UK English Female'); // Native <-> fallback switches are reported via the OnServiceSwitched event rv.addEventListener('OnServiceSwitched', (payload) => { console.log(`Switched from ${payload.from} to ${payload.to}`); }); ``` ## Forcing Fallback Force server (fallback) audio even when native voices exist — set it at init or toggle at runtime: ```typescript // At init const rv = await getResponsiveVoice({ apiKey: 'YOUR_API_KEY', forceFallback: true, }); // Or at runtime rv.setForceFallback(true); ``` ## User Interaction Requirements Modern browsers require user interaction before playing audio: ```typescript // ❌ This may be blocked window.onload = () => { rv.speak('Hello'); // Blocked by autoplay policy }; // ✅ This works button.onclick = () => { rv.speak('Hello'); // Allowed - user initiated }; ``` ### Workaround: Initialize on First Interaction On mobile, ResponsiveVoice’s built-in permission prompt already unlocks audio on the first gesture — use this manual pattern only if you’ve disabled the prompt and want to unlock from your own handler: ```typescript let initialized = false; document.addEventListener( 'click', () => { if (!initialized) { rv.speak('', 'UK English Female'); // Silent init initialized = true; } }, { once: true }, ); ``` ## Testing Across Platforms ```typescript // Log platform info for debugging console.log({ browser: navigator.userAgent, hasNativeTTS: 'speechSynthesis' in window, nativeVoices: window.speechSynthesis?.getVoices().length ?? 0, rvVoices: rv.getVoices().length, }); ``` # Events > Handling speech events and callbacks ## Event System ResponsiveVoice gives you two ways to observe speech: * **Per-call callbacks** — passed inline to `speak()`, bound to that one utterance, and reset on the next `speak()`. * **Global events** — registered once with `addEventListener()`, firing across every `speak()` call. Tip Try the [**live Events & Callbacks demo**](https://examples.responsivevoice.org/browser/events-callbacks/) — every per-call callback and global event lights up as you click Speak. The companion [docs page](/examples/events-callbacks) explains the design. ## Per-call callbacks Pass these in the options object of `speak(text, voice?, options?)`. They belong to a single utterance. | Callback | Signature | Fires | | ------------ | ------------------------------------------- | ---------------------------------------------- | | `onstart` | `() => void` | speech begins | | `onend` | `() => void` | speech completes | | `onerror` | `(error: Error) => void` | an error occurs | | `onboundary` | `(charIndex: number, name: string) => void` | crosses a word/sentence boundary (native only) | ```typescript rv.speak('Hello world', 'UK English Female', { onstart: () => console.log('Started speaking'), onend: () => console.log('Finished speaking'), onerror: (error) => console.error('Error:', error.message), onboundary: (charIndex, name) => { console.log(`Boundary (${name}) at character ${charIndex}`); }, }); ``` Note `onboundary` only fires with native browser voices, not the fallback (server) audio engine. ## Global events Register once with `addEventListener(name, handler)`; they fire across every `speak()` call. Event names are PascalCase: | Event | Payload | Fires | | ---------------------- | --------------------------------- | ------------------------------------------- | | `OnReady` | — | client initialized and ready | | `OnLoad` | — | alias of `OnReady` (legacy) | | `OnStart` | — | an utterance starts | | `OnEnd` | — | an utterance ends | | `OnPause` | — | speech is paused | | `OnResume` | — | speech resumes | | `OnError` | `{ error, message? }` | an error occurs | | `OnVoiceResolved` | voice-resolution details | a voice is resolved for an utterance | | `OnServiceSwitched` | `{ from, to }` | engine switches between native and fallback | | `OnPartStart` | `{ partIndex, totalParts, text }` | a text chunk starts speaking | | `OnPartEnd` | `{ partIndex, totalParts, text }` | a text chunk finishes | | `OnClickEvent` | — | a user gesture (click) is detected | | `OnAllowSpeechClicked` | `{ allowed }` | user responds to the permission prompt | ```typescript rv.addEventListener('OnStart', () => console.log('Speech started')); rv.addEventListener('OnPause', () => console.log('Speech paused')); rv.addEventListener('OnResume', () => console.log('Speech resumed')); ``` Pause and resume are **only** global events — they are not per-call callbacks. See the [`RVEventType` reference](/api/types/src/#rveventtypeschema) for the complete list of event names. ### Removing listeners Pass the same handler reference you registered: ```typescript const handler = () => console.log('Speech started'); rv.addEventListener('OnStart', handler); rv.removeEventListener('OnStart', handler); ``` ## Error handling The per-call `onerror` callback receives a standard [`Error`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Error). The `OnError` event delivers a payload `{ error, message? }` whose `error` is that same `Error`: ```typescript rv.speak('Hello', 'UK English Female', { onerror: (error) => { console.error('Speech failed:', error.message); }, }); ``` ## Async/await pattern Wrap `speak()` in a Promise using `onend` and `onerror`: ```typescript function speakAsync(text: string, voice: string): Promise { return new Promise((resolve, reject) => { rv.speak(text, voice, { onend: () => resolve(), onerror: (error) => reject(error), }); }); } // Usage async function readParagraphs(paragraphs: string[]) { for (const paragraph of paragraphs) { await speakAsync(paragraph, 'UK English Female'); } console.log('All paragraphs read'); } ``` ## Progress tracking UI Use `onboundary` to update a progress bar as speech advances (native voices only): ```typescript let startTime: number; rv.speak(longText, 'UK English Female', { onstart: () => { startTime = Date.now(); progressBar.style.width = '0%'; }, onboundary: (charIndex) => { const progress = (charIndex / longText.length) * 100; progressBar.style.width = `${progress}%`; }, onend: () => { progressBar.style.width = '100%'; console.log(`Completed in ${Date.now() - startTime}ms`); }, }); ``` ## Queue events Each `speak()` call carries its own callbacks: ```typescript rv.speak('First sentence', 'UK English Female', { onend: () => console.log('First done, starting second'), }); rv.speak('Second sentence', 'UK English Female', { onstart: () => console.log('Second starting'), onend: () => console.log('Queue complete'), }); ``` # Frequently Asked Questions > Common questions about ResponsiveVoice — pricing, browser and Node.js support, API keys, streaming, and available voices. ResponsiveVoice is TypeScript-first text-to-speech for browsers and Node.js. Answers to the questions we hear most often are below. ## Is ResponsiveVoice free to use? Yes. The ResponsiveVoice library is open source and free to use from npm or a CDN. Create a free account at [responsivevoice.org/register](https://responsivevoice.org/register) — it takes a few seconds — to unlock free server voices for your site. Without an account the library runs in demo mode. Paid plans add features such as streaming, and premium voice providers (Microsoft Azure, OpenAI, Google Cloud) are supported via Bring Your Own Key (BYOK). ## Which browsers and runtimes are supported? ResponsiveVoice runs in all evergreen browsers and in Node.js, using the native Web Speech API where available and server voices (with an account) otherwise. See the [Browser Support](/guides/browser-support/) guide for the full compatibility matrix. ## Do I need an API key? Yes, to use server voices — and it’s free. Register an account to get one. The key is a website identity, not a secret: it’s tied to your registered domain, so it’s safe to include in client-side code. Without a key, the library runs in demo mode. ## Does ResponsiveVoice support streaming audio? Yes — on higher-tier plans. Audio is delivered as it’s synthesized via HTTP audio streaming or WebSocket streaming, so playback can start before the full clip is ready. Other tiers return the complete audio in a single response. ## How many voices and languages are available? The base catalog includes 100+ voices across 50+ languages and genders, chosen through the [voice resolution chain](/guides/voice-selection/) (native Web Speech or fallback). Bring Your Own Key (BYOK) providers add their own voices on top — growing the catalog to thousands. ## Does ResponsiveVoice work on iOS? Yes. iOS (and some mobile browsers) require a user gesture before audio can play, and ResponsiveVoice handles that automatically — it shows a built-in permission prompt that captures the first tap and unlocks audio, so you don’t need to add your own button. The prompt is customizable, or you can disable it and trigger speech from your own UI instead. ## How can I improve speech quality? Punctuation shapes pacing and emphasis — add commas and periods for natural pauses. For tricky pronunciations, respell a word phonetically, add hyphens between syllables, or spell it out letter by letter. You can also configure text replacements for consistent pronunciation of names and domain terms. ## Can I change the speaking rate, pitch, and volume? Yes. Set `rate` and `pitch` (0–2, default 1) and `volume` (0–1, default 1) per request. Native browser voices (Web Speech API) apply them directly; for voices that synthesize server-side — API-only voices, or when the browser lacks the requested voice — how each adjustment applies depends on that provider. ## Does ResponsiveVoice support SSML? SSML (voice markup) is part of the Web Speech API specification, but no current browser actually implements it, and there’s no announced commitment to add it. So ResponsiveVoice takes plain text — shape delivery with the `rate`, `pitch`, and `volume` parameters, plus punctuation and text replacements for pacing and pronunciation. If browsers add SSML support, ResponsiveVoice will adopt it. # Text Chunking > How ResponsiveVoice handles long text ## Overview ResponsiveVoice automatically splits long text into smaller chunks for optimal playback. This ensures reliable speech synthesis even with large amounts of text. ## Why Chunking? * **API Limits**: TTS services have character limits per request * **Memory**: Smaller audio buffers are more efficient * **Responsiveness**: Speech starts faster with smaller chunks * **Reliability**: Reduces chance of timeouts and errors ## Automatic Chunking Chunking happens automatically when you call `speak()`: ```typescript // This long text is automatically split into chunks responsiveVoice.speak( ` Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris. `, 'UK English Female', ); ``` ## Chunk Boundaries Within the character limit, text is split at the latest (rightmost) natural boundary, preferring higher-priority delimiters first: 1. **Sentence endings** (`.` `?` `!`) 2. **Major separators** (`;` `:`) 3. **Clause separators** (`,`) 4. **Word boundaries** (spaces) 5. **CJK character boundaries** (between ideographs, when no earlier boundary fits) Tip The chunker never splits in the middle of a word, and skips `.`/`,` inside numbers like `3.14` or `1,000`. ## Default Limits | Setting | Value | Description | | ------------------ | --------- | -------------------------------------- | | Default chunk size | 200 chars | Characters per chunk when not set | | Min chunk size | 50 chars | Lower bound (smaller values clamp up) | | Max chunk size | 300 chars | Upper bound (larger values clamp down) | | Sentence priority | High | Prefers sentence boundaries | These apply to voices that play on the device. Voices served by the API use a larger chunk that `characterLimit` does not lower. ## Custom Configuration The chunk character limit is a page-wide setting. Set it globally: * Browser bundle (CDN) ```html ``` * npm (ESM) ```typescript import { getResponsiveVoice } from '@responsivevoice/core'; // At startup const rv = await getResponsiveVoice({ characterLimit: 150 }); // clamped to 50–300 // Or at runtime rv.setCharacterLimit(150); rv.getCharacterLimit(); // 150 ``` Note Examples below use the browser bundle’s global `responsiveVoice`. With the npm package, call the same methods on the instance returned by `getResponsiveVoice()`. ## Seamless Playback Chunks are queued and played sequentially without gaps: ```typescript responsiveVoice.speak(longArticle, 'UK English Female', { onstart: () => console.log('Started reading article'), onend: () => console.log('Finished reading article'), // onend fires once when ALL chunks complete }); ``` ## Manual Chunking For complete control, split the text yourself with `chunkText()`. Each chunk is `{ text, index, total, isLast }`: ```typescript const chunks = responsiveVoice.chunkText(article, { characterLimit: 200 }); for (const chunk of chunks) { await new Promise((resolve) => responsiveVoice.speak(chunk.text, 'UK English Female', { onend: resolve }), ); } ``` `chunkText` is also importable standalone from `@responsivevoice/text`. ## Progress with Chunks Track progress across chunks: ```typescript const chunks = responsiveVoice.chunkText(longText, { characterLimit: 200 }); let currentChunk = 0; function speakNextChunk() { if (currentChunk >= chunks.length) { console.log('Complete!'); return; } const chunk = chunks[currentChunk]; const progress = ((chunk.index + 1) / chunk.total) * 100; console.log(`Progress: ${progress.toFixed(0)}%`); responsiveVoice.speak(chunk.text, 'UK English Female', { onend: () => { currentChunk++; speakNextChunk(); }, }); } speakNextChunk(); ``` ## Special Characters The chunker is aware of: * **Numbers**: won’t split on `.` or `,` inside a number (`3.14`, `1,000`) * **Grouping pairs**: prefers not to split inside quotes, parentheses, or brackets ```typescript // Numbers stay intact responsiveVoice.speak('The price is $19.99 per month.', voice); // Quoted text is kept together responsiveVoice.speak('She said "Hello there, friend!" and waved.', voice); ``` ## SSML ResponsiveVoice takes **plain text**, not SSML — no current browser implements the Web Speech API’s SSML, so it isn’t supported. Shape delivery with the `rate`, `pitch`, and `volume` parameters plus punctuation. See the [FAQ](/guides/faq/#does-responsivevoice-support-ssml). ## Performance Tips 1. **Pre-chunk large texts** for better control 2. **Keep `characterLimit` within range** (50–300; default 200) 3. **Avoid very small chunks** (causes choppy playback) 4. **Consider caching** for repeated content # Voice Resolver Hook > Intercept and transform voice selectors before resolution ## Overview The `resolveVoice` hook lets integrating applications intercept the voice selector passed to `speak()` and transform it before the voice matching chain runs. This is useful when an external system passes voice names that don’t match ResponsiveVoice’s voice catalogue — instead of modifying every call site, a single hook can remap, normalize, or redirect selectors. ## Usage ```typescript import { getResponsiveVoice } from '@responsivevoice/core'; const rv = await getResponsiveVoice({ apiKey: 'YOUR_KEY', resolveVoice: (selector) => { if (typeof selector === 'string') { const aliases: Record = { 'Google UK English Female': 'UK English Female', 'Microsoft Zira': 'US English Female', }; return aliases[selector] ?? selector; } return selector; }, }); // "Google UK English Female" is silently remapped to "UK English Female" rv.speak('Hello', 'Google UK English Female'); ``` ## Hook Signature ```typescript type ResolveVoiceHook = ( selector: VoiceSelector | undefined, ) => VoiceSelector | undefined; ``` | Parameter | Type | Description | | ----------- | ---------------------------- | ----------------------------------------------------------------------- | | `selector` | `VoiceSelector \| undefined` | The incoming voice selector, or `undefined` when no voice was specified | | **Returns** | `VoiceSelector \| undefined` | A transformed selector, or `undefined` to use `defaultVoice` | `VoiceSelector` is a union of three forms (the post-parse output the hook receives): | Type | JS form | Wire form | Meaning | | --------------- | --------------------- | ------------------------------------ | -------------------------------- | | `string` | `'UK English Female'` | same | Resolve by exact voice name | | `RegexSelector` | `/Portuguese/` | `{ regex: 'Portuguese', flags: '' }` | First voice matching the pattern | | `VoiceQuery` | `{ lang: 'pt' }` | same | Structured filter (AND logic) | The hook receives the post-parse `VoiceSelector`, where any incoming JS `RegExp` has already been normalized to the `{ regex, flags }` literal form. The hook’s return value can be either form — a returned `RegExp` is normalized the same way before reaching the resolver. ## Return Value Semantics | Hook returns | What happens | | ----------------------------- | ----------------------------------------------------- | | A `string` | Resolves by name (exact match, then fallback chain) | | A `RegExp` or `RegexSelector` | Resolves by pattern (first match) | | A `VoiceQuery` | Resolves by structured query (lang, gender, provider) | | `undefined` | Falls through to the configured `defaultVoice` | Caution The hook does **not** fire when `params.voice` is set. That escape hatch passes a raw `SpeechSynthesisVoice` object and bypasses the entire resolution chain, including the hook. ## Patterns ### Static aliasing Map legacy or external voice names to ResponsiveVoice names: ```typescript resolveVoice: (selector) => { if (typeof selector === 'string') { const map: Record = { 'old-voice-name': 'UK English Female', 'legacy-male': 'US English Male', }; return map[selector] ?? selector; } return selector; }, ``` ### Locale-based routing Redirect to a locale-appropriate voice when the exact name doesn’t exist: ```typescript resolveVoice: (selector) => { if (typeof selector === 'string' && !knownVoices.has(selector)) { const locale = extractLocale(selector); const gender = extractGender(selector); return { lang: locale, gender }; } return selector; }, ``` ### Debug logging Observe what selectors are being passed without changing behavior: ```typescript resolveVoice: (selector) => { console.log('[RV] resolving voice:', selector); return selector; }, ``` ## TypeScript Both `ResolveVoiceHook` and `VoiceSelector` are exported from `@responsivevoice/core`: ```typescript import type { ResolveVoiceHook, VoiceSelector } from '@responsivevoice/core'; const myHook: ResolveVoiceHook = (selector) => { // your logic return selector; }; ``` # Voice Selection > How to select voices using names, patterns, and structured queries The second argument to `speak()` is a **voice selector** — a `string`, `RegExp`, or structured query that tells ResponsiveVoice which voice to use: ```typescript rv.speak(text: string, voice?: VoiceSelectorInput, params?: SpeakOptions): void; ``` `VoiceSelector` (the post-parse, on-the-wire form) is a union of three JSON-serializable shapes: | Type | JS form | Wire form | Description | | --------------- | --------------------- | ------------------------------------- | -------------------------------- | | `string` | `'UK English Female'` | same | Exact voice name | | `RegexSelector` | `/Portuguese/i` | `{ regex: 'Portuguese', flags: 'i' }` | First voice matching the pattern | | `VoiceQuery` | `{ lang: 'pt' }` | same | Structured filter (AND logic) | In JS code, pass a real `RegExp` literal and the schema normalizes it to the JSON-clean `{ regex, flags }` form on parse — the resolver, server payloads, and SDKs all see the wire form, so the contract is identical across every language. ## By Name The simplest form — pass a ResponsiveVoice voice name as a string: ```typescript rv.speak('Hello', 'UK English Female'); rv.speak('Hello', 'US English Male'); rv.speak('Bonjour', 'French Female'); rv.speak('Hallo', 'Deutsch Male'); ``` If the exact name isn’t available on the current platform, the [voice matching chain](#voice-matching-chain) kicks in to find the closest alternative. ## By Pattern Pass a `RegExp` to match against all non-deprecated voice names. The first match wins: ```typescript rv.speak('Olá', /Portuguese/); rv.speak('Hello', /English.*Female/i); ``` In server-side config or non-JS SDKs (Python, Go, PHP, Java), use the JSON literal form instead — it’s the same selector after the schema normalizes: ```json { "regex": "Portuguese" } { "regex": "English.*Female", "flags": "i" } ``` Tip Pattern matching is useful when you want any voice from a family (e.g., any Portuguese variant) rather than a specific voice name. ## By Query Pass a `VoiceQuery` object to filter voices by attributes. All conditions are AND-ed — a voice must match every specified field: | Field | Type | Behavior | | ---------- | ---------------------------------- | --------------------------------------------------- | | `name` | `string` | Case-insensitive; exact match first, then substring | | `lang` | `string` | BCP-47 prefix match (`"pt"` matches `"pt-BR"`) | | `gender` | `'f' \| 'm' \| 'male' \| 'female'` | Gender filter | | `isByok` | `boolean` | Filter to BYOK (Bring Your Own Key) voices only | | `provider` | `string` | Provider name, case-insensitive | ```typescript // By language rv.speak('Bonjour', { lang: 'fr' }); // By language + gender rv.speak('Olá', { lang: 'pt', gender: 'f' }); // By provider (BYOK voices) rv.speak('Hello', { provider: 'Google Cloud WaveNet', lang: 'en-GB', gender: 'm', }); ``` ## Direct Voice Override When you have a raw `SpeechSynthesisVoice` object from the browser’s Web Speech API, you can pass it directly via the `params.voice` option: ```typescript const nativeVoices = speechSynthesis.getVoices(); const samantha = nativeVoices.find((v) => v.name === 'Samantha'); rv.speak('Hello', undefined, { voice: samantha }); ``` Caution This bypasses the entire ResponsiveVoice resolution chain, including the [`resolveVoice` hook](/guides/voice-resolver/). The voice must be a valid `SpeechSynthesisVoice` from the current browser. ## Default Voice When no voice selector is passed to `speak()`, the configured default voice is used. The built-in default is `'UK English Female'`. ```typescript // Set at init const rv = await getResponsiveVoice({ apiKey: 'YOUR_KEY', defaultVoice: 'US English Female', }); rv.speak('Uses US English Female'); // Change at runtime rv.setDefaultVoice('French Male'); rv.speak('Uses French Male now'); ``` ## Language-Aware Website Widgets ResponsiveVoice accepts a language query, but your interface decides when to change languages. A multilingual website widget should normally use this precedence: 1. A voice explicitly chosen by the visitor 2. Language detected from text the visitor typed 3. The browser’s preferred language 4. English fallback using `UK English Female` ```typescript function browserLanguage(): string { return (navigator.languages?.[0] || navigator.language || 'en') .toLowerCase() .split('-')[0]; } function selectorForLanguage(language: string) { const lang = language.toLowerCase().split('-')[0]; // Keep the English experience consistent and premium by default. if (lang === 'en') return 'UK English Female'; return { lang, gender: 'f' } as const; } function speakVisitorText(text: string, selectedVoice?: string) { // Supply this with your preferred client-side or server-side detector. const typedLanguage = text.length >= 20 ? detectLanguage(text) : undefined; const language = typedLanguage || browserLanguage(); const voice = selectedVoice || selectorForLanguage(language); rv.speak(text, voice); } ``` Use browser language to initialize placeholder text and the first suggested voice. Re-run language detection after the visitor types enough text to make a useful decision, but do not override a manual voice selection. Note The SDK does not guess the language of arbitrary text. Keep detection in the application so you can choose the detector, confidence threshold, privacy behavior, and fallback policy that fit your site. ## Force Fallback Set `forceFallback` to skip native browser voices entirely and always use server-side HTTP audio. This provides consistent voice quality across all browsers: ```typescript const rv = await getResponsiveVoice({ apiKey: 'YOUR_KEY', forceFallback: true, }); // Toggle at runtime rv.setForceFallback(false); ``` ## Resolution Precedence When `speak()` is called, voice selection resolves in this order (highest priority first): 1. **`params.voice` override** — direct `SpeechSynthesisVoice` object; bypasses everything below 2. **[`resolveVoice` hook](/guides/voice-resolver/)** — intercepts and transforms the selector before resolution 3. **Explicit `VoiceSelector`** — the `string`, `RegExp` (or its `{ regex, flags? }` wire form), or `VoiceQuery` passed to `speak()` 4. **`defaultVoice` config** — used when no selector is provided ## Voice Matching Chain Once a voice name is determined, ResponsiveVoice walks the voice’s internal chain of system voice IDs and tries these matching strategies **in order across all chain entries** (strategy-first): 1. **Exact match** — direct name comparison against browser voices 2. **Whitespace normalized** — handles Chrome’s Unicode non-breaking spaces (U+00A0) in Asian voice names 3. **Parenthetical stripped** — handles Apple Safari’s “(Enhanced)” / “(Premium)” suffixes 4. **Partial match** — case-insensitive substring match 5. **Language fallback** — any browser voice matching the target language 6. **HTTP fallback** — server-side TTS via the ResponsiveVoice API Tip Strategy-first iteration means an exact match later in the chain always beats a partial match earlier in the chain. This ensures the most specific match wins. ## Listing Voices Use `getVoices()` to list all available voices on the current platform: ```typescript const voices = rv.getVoices(); console.log(voices); // [ // { name: 'UK English Female', lang: 'en-GB', gender: 'f' }, // { name: 'UK English Male', lang: 'en-GB', gender: 'm' }, // { name: 'US English Female', lang: 'en-US', gender: 'f' }, // ... // ] ``` ## Platform-Specific Voices Some voices are only available on specific platforms: | Platform | Notes | | -------- | ------------------------------------ | | Chrome | Google voices, best selection | | Safari | Apple voices, “(Enhanced)” variants | | Firefox | Limited native voices, uses fallback | | iOS | Version-specific voice sets | | Android | Device-dependent voices | ## Language Codes Voices use BCP-47 language codes: | Code | Language | | ------- | -------------------- | | `en-GB` | British English | | `en-US` | American English | | `fr-FR` | French | | `de-DE` | German | | `es-ES` | Spanish | | `ja-JP` | Japanese | | `zh-CN` | Chinese (Simplified) | ## Example: Voice Selector UI Try all three selector forms side by side in the [**live Voice Selector demo**](https://examples.responsivevoice.org/browser/voice-selector/) — type a name, type a regex, or build a query and watch the snippet rewrite as you switch tabs. The accompanying [docs page](/examples/voice-selector) explains the design choices. The minimal pattern below shows the simplest case (build a dropdown from `getVoices()`): ```typescript // Build a voice selector dropdown const select = document.createElement('select'); rv.getVoices().forEach((voice) => { const option = document.createElement('option'); option.value = voice.name; option.textContent = `${voice.name} (${voice.lang})`; select.appendChild(option); }); select.addEventListener('change', () => { rv.speak('Sample text', select.value); }); ``` # Web Player > Configure the web player theme, layout, controls, behavior, and advanced narration options. The **web player** is a drop-in article reader with paragraph highlighting, click-to-jump, playback controls, and a floating mini-player that follows the reader once the main controls scroll out of view. You can add a default web player to any page **without code** from the [ResponsiveVoice Dashboard](/guides/app-features/#web-player). This guide covers the **code path** — installing, configuring, and overriding the player programmatically with `init()` and `mount()`. For live examples, see the [Web Player example](/examples/web-player/) and [Web Player Customization](/examples/web-player-customization/) pages. ## At a Glance 1. Drop the CDN script in your page and call: ```javascript responsiveVoice.init({ apiKey: 'YOUR_API_KEY', features: { webPlayer: { enabled: true }, }, }); ``` 2. Choose a **Theme**: `neutral`, `responsivevoice`, or custom color tokens. 3. Set the **Layout** if the player should appear somewhere other than before the article. 4. Choose **Controls** such as progress, time, skip, speed, brand, and mini-player settings. 5. Tune **Behavior** such as paragraph highlighting, click-to-jump, and skipping non-readable content. 6. Use **Advanced** only when you need custom selectors, voice override, or extra narration exclusions. ## Add the Script and Initialize Drop the CDN bundle into your page and turn the player on. By default it attaches to the first `
` element it finds and narrates every `p`, `h2`, `h3`, and `li` inside it. ```html ``` If your page has an `
`, the player appears above it after initialization. Note `init()` is async because it fetches your website configuration. The player mounts once initialization resolves; you do not need to `await` anything for a static `
` already in the DOM. ## Theme optional Theme controls the visual style of the player. Two presets ship with the player, plus a custom-token path for brand colors. Tabs preserve your selection across pages. * Neutral The default theme is light, minimal, and designed to sit comfortably on most host pages. No extra configuration is needed: ```js window.responsiveVoice.init({ apiKey: 'YOUR_API_KEY', features: { webPlayer: { enabled: true }, }, }); ``` * ResponsiveVoice The ResponsiveVoice theme uses the same controls and layout, recolored around the ResponsiveVoice violet. ```js window.responsiveVoice.init({ apiKey: 'YOUR_API_KEY', features: { webPlayer: { enabled: true, theme: 'responsivevoice' }, }, }); ``` * Custom tokens Override individual color tokens to match your brand. Every field is optional; omitted values fall back to the neutral defaults. ```js window.responsiveVoice.init({ apiKey: 'YOUR_API_KEY', features: { webPlayer: { enabled: true, theme: { fill: '#10b981', track: '#d1fae5', accentSoft: '#d1fae5', }, }, }, }); ``` The full nine-token palette is `bg`, `fg`, `muted`, `accent`, `accentSoft`, `hover`, `border`, `track`, and `fill`. The [customization picker](https://examples.responsivevoice.org/browser/web-player-customization/) lets you tune those values and copy the matching configuration. ## Layout optional Layout controls where the player appears and how much space it uses. | Config option | Default | What it does | | ---------------- | ---------- | -------------------------------------------------------------------------------------------------------------- | | `position` | `'before'` | Keyword (`before` / `after` / `inline`) relative to `selector`, or `{ target, at }` for a custom mount target. | | `layout.mode` | `'shrink'` | `shrink` hugs the controls; `fill` spans the container. | | `layout.display` | `'block'` | `block` gives the player its own line; `inline` flows with surrounding content. | ```js window.responsiveVoice.init({ apiKey: 'YOUR_API_KEY', features: { webPlayer: { enabled: true, position: 'before', layout: { mode: 'shrink', display: 'block', }, }, }, }); ``` For a custom mount target, pass an object: ```js window.responsiveVoice.init({ apiKey: 'YOUR_API_KEY', features: { webPlayer: { enabled: true, position: { target: '#article-player', at: 'inside', }, }, }, }); ``` ## Controls optional Controls decide which buttons are visible and whether the floating mini-player is enabled. | Config option | Default | What it does | | ---------------------- | --------------- | --------------------------------------------------------------------------------------------------------- | | `controls.progress` | `true` | Shows progress through the readable content. | | `controls.time` | `true` | Shows elapsed and total estimated time. | | `controls.skip` | `true` | Shows previous and next paragraph buttons. | | `controls.speed` | `true` | Shows the speed cycle button. | | `controls.brand` | `true` | Shows the ResponsiveVoice brand icon and the “Powered by” line; use `{ icon, poweredBy }` to toggle each. | | `miniPlayer.enabled` | `true` | Shows the mini-player when the main player is out of view. | | `miniPlayer.position` | `'bottom-left'` | Places the mini-player in a viewport corner or custom offset. | | `miniPlayer.animation` | `'slide'` | Uses `none`, `fade`, `slide`, or `pop`. | ```js window.responsiveVoice.init({ apiKey: 'YOUR_API_KEY', features: { webPlayer: { enabled: true, controls: { progress: true, time: true, skip: true, speed: true, brand: true, }, miniPlayer: { enabled: true, position: 'bottom-left', animation: 'slide', }, }, }, }); ``` ## Behavior optional Behavior covers highlighting, click-to-jump, and skipping non-readable content. | Config option | Default | What it does | | ------------------------------- | ------- | ------------------------------------------------------------------------------------------- | | `navigation.paragraphHighlight` | `true` | Highlights the currently spoken element. | | `navigation.paragraphClick` | `true` | Lets visitors click a paragraph to start reading there. | | `sanitize.enabled` | `true` | Excludes scripts, styles, form controls, embedded media, and hidden content from narration. | ```js window.responsiveVoice.init({ apiKey: 'YOUR_API_KEY', features: { webPlayer: { enabled: true, navigation: { paragraphHighlight: true, paragraphClick: true, }, sanitize: { enabled: true }, }, }, }); ``` Keep `sanitize.enabled` on for normal sites. Turning it off narrates raw matched text and is mainly useful for debugging unusual markup. ## Advanced optional Advanced options are for site-specific markup and voice overrides. Most websites can leave these at their defaults. Use them when the player needs to read a specific part of a page, skip extra elements, or use a different voice from the website default. | Config option | Default | What it does | | ------------------- | ----------------- | ------------------------------------------------------------------- | | `selector` | `'article'` | CSS selector for the element that contains readable content. | | `paragraphSelector` | `'p, h2, h3, li'` | CSS selector for readable elements inside the container. | | `voice` | Website default | Optional voice selector for this player only. | | `sanitize.exclude` | `[]` | Extra CSS selectors to skip in addition to the built-in exclusions. | ```js window.responsiveVoice.init({ apiKey: 'YOUR_API_KEY', features: { webPlayer: { enabled: true, selector: '.post-body', paragraphSelector: 'p, h2, h3, blockquote', voice: 'UK English Female', sanitize: { enabled: true, exclude: ['.pull-quote', '.byline'], }, }, }, }); ``` If `selector` matches more than one element on the page, every match gets its own independent player. Starting playback on one automatically pauses every other player. Nested matches, such as an `
` inside another `
`, are filtered to the outermost element so duplicate players are not created. ### Place the Player in a specific container Many WordPress themes and marketing sites wrap the entire page in an `
`. In that layout, the default `selector: 'article'` can place the player above the page title and narrate calls to action or other content that is not part of the listening experience. Use a purpose-built content container and player slot instead: ```html

A better way to experience the web

Visitors can listen while they commute, work, or rest their eyes.

The current passage is highlighted as the article is read.

``` ```js window.responsiveVoice.webPlayer?.mount('#article-preview', { voice: 'UK English Female', theme: 'responsivevoice', paragraphSelector: 'h2, p', position: { target: '#article-player', at: 'inside', }, }); ``` This pattern is useful for product pages too: place a short, representative article demo after trust proof such as customer logos, then let visitors try the same player their readers would receive. ### Voice Override Without configuration, the player narrates with the website default voice. To override per player, add a `voice` field. It accepts a name string, a `RegExp`, or a structured query, exactly like the second argument to `speak()`. ```js window.responsiveVoice.init({ apiKey: 'YOUR_API_KEY', features: { webPlayer: { enabled: true, voice: 'UK English Female', rate: 0.95, }, }, }); ``` The full grammar lives in [Voice Selection](/guides/voice-selection/). The same `voice`, `rate`, `pitch`, and `volume` fields are also valid as per-mount overrides on `mount()`. ### Exclude From Narration Mark any element with `data-rv-skip` to keep it out of the narration. The player still highlights surrounding paragraphs; the skipped element is invisible to the reader’s progression. ```html

Read this paragraph.

Continue from here.

``` Useful for code blocks, figure captions, ad slots, pull quotes, and bylines. By default the player never reads content that is not visible prose. Scripts, styles, form controls, embedded media, and elements marked `hidden` or `aria-hidden="true"` are excluded from narration even when they sit inside a matched paragraph. A paragraph left empty after sanitization is dropped from the reading flow. Add your own selectors with `sanitize.exclude`. Those selectors are added to the built-in exclusions; they do not replace the built-in list. ## Testing After integrating, verify on the real page — its markup determines what’s found and narrated: * Press play and confirm the selected voice sounds right. * Confirm the configured controls (progress, time, skip, speed, brand) appear. * Click a paragraph if `navigation.paragraphClick` is enabled. * Confirm highlighting appears if `navigation.paragraphHighlight` is enabled. * Scroll until the main player leaves view and confirm the mini-player appears if enabled. * Add a known excluded element and confirm it is skipped. ## Dynamic Content For SPAs and lazy-loaded sections, the article may not be in the DOM when `init()` runs, so the auto-discovery pass misses it. Use `webPlayer.mount()` to attach a player after the element is rendered: ```js const html = await fetch('/posts/123').then((r) => r.text()); document.getElementById('post-area').innerHTML = html; const handle = window.responsiveVoice.webPlayer?.mount('#post-area'); // later, before removing the element: handle?.unmount(); ``` `mount()` accepts the same option shape as the init config, leaf-merged over it. For example, `mount('#post-area', { rate: 1.2 })` keeps every other setting intact and only changes the rate. It returns `null` if the feature is not enabled or the element cannot be found, so the `?.` guards are intentional. Caution Always call `handle.unmount()` before you remove the host element from the DOM. The player keeps DOM listeners attached to the article and the document. A forgotten unmount will not crash anything, but it can leak event handlers across SPA navigations. ## Troubleshooting ### The player doesn’t appear * Confirm `init()` ran and resolved, and `features.webPlayer.enabled` is `true`. * Confirm `selector` matches an element that is already in the DOM when `init()` runs. Content rendered later (SPA, lazy-load) is missed by the one-time auto-discovery pass — mount it explicitly with [`mount()`](#dynamic-content). ### `mount()` returned `null` It returns `null` when the feature isn’t enabled or the target element isn’t in the DOM — which is why the `?.` guards matter. Confirm `enabled: true` and that the element exists before the call. ### The wrong content is read, or paragraphs are missing * `paragraphSelector` must match the elements you want narrated (default `p, h2, h3, li`). * Sanitization drops scripts, styles, form controls, media, and `hidden` / `aria-hidden` nodes, plus anything matching `data-rv-skip` or your `sanitize.exclude` selectors; a paragraph left empty after sanitization is skipped. ### The mini-player doesn’t appear `miniPlayer.enabled` must be `true`, and it only surfaces while playback is active and the main player has scrolled out of view. ### Handlers leak across SPA navigations Call `handle.unmount()` before removing the host element — the player keeps listeners on the article and document until you do. ## Next Steps [Live demo](https://examples.responsivevoice.org/browser/web-player/)The basic integration running in your browser. Source on GitHub, ready to copy and adapt. [Customize live](https://examples.responsivevoice.org/browser/web-player-customization/)Tweak every option in real time. The matching init() and mount() snippets update alongside the player. [Web Player example walkthrough](/examples/web-player/)In-docs walkthrough of multi-mount, imperative mount, and skipped content. [Voice Selection](/guides/voice-selection/)The selector grammar used by the voice field: name string, regex, or structured query. # Why ResponsiveVoice? > How ResponsiveVoice v2 compares to ElevenLabs, Microsoft Azure, and Google Cloud for adding text-to-speech to a website. ResponsiveVoice is an open-source, TypeScript-first text-to-speech layer for the web. It is the integration and control layer between your site and one or more voice engines — browser-native, ResponsiveVoice’s hosted voices, or premium providers you bring yourself. Note ResponsiveVoice v2 is a complete rewrite of the original library — MIT-licensed, fully typed, and split into focused packages. Older reviews describing it as a fixed-price Web Speech API wrapper predate this release. ## What ResponsiveVoice gives you * **Open source, MIT-licensed.** The client (`@responsivevoice/core`) is a drop-in replacement for the legacy `responsivevoice.js` — same `speak`/`cancel`/`pause`/`resume` API, now typed and tree-shakeable. * **Browser-native with automatic fallback.** Uses the Web Speech API when the device has a matching voice, and falls back to hosted server voices when it doesn’t — one API, consistent behavior across browsers. * **100+ voices across 50+ languages**, fetched and cached at runtime, so the catalog improves without a package upgrade. * **Bring Your Own Key (BYOK).** Route premium voices from **Google Cloud**, **Microsoft Azure**, and **OpenAI** through ResponsiveVoice using your own provider key — you keep the provider relationship and the per-character billing, and gain RV’s browser integration, fallback, streaming, and player features on top. * **Streaming playback.** HTTP audio or WebSocket, so speech starts before the full clip is ready. * **Predictable pricing.** A free plan plus fixed-tier plans, instead of metering every character. * **REST + WebSocket API** documented by an OpenAPI 3.1 specification, for server-side and non-browser use. ## How it compares The premium providers below produce excellent neural audio. The distinction is that ResponsiveVoice is the **integration layer** — and with BYOK it can front those same providers rather than competing with them. | Capability | ResponsiveVoice | ElevenLabs | Azure / Google Cloud | | ------------------------- | -------------------------------- | ---------------------------- | -------------------- | | Open source (MIT) | Yes | No | No | | Drop-in browser script | Yes | Via your own backend | Via your own backend | | Browser-native + fallback | Yes | Cloud only | Cloud only | | Premium neural voices | Via BYOK (Azure, OpenAI, Google) | Native | Native | | Voice cloning | No | Yes | Limited | | Pricing model | Free plan + fixed tiers | Per-character / subscription | Per-character | | Streaming | HTTP + WebSocket | Yes | Yes | ## When to choose what * **Choose ResponsiveVoice** when you want a drop-in, open-source browser TTS with native-plus-fallback behavior, predictable pricing, and the option to bring premium provider voices via BYOK without re-architecting — read-aloud, accessibility, language learning, article narration, and announcements. * **Choose a provider directly** when best-in-class voice cloning or maximum expressive realism is the product itself, and you’re set up to manage the integration and per-character billing. Many sites use both: ResponsiveVoice for the browser integration and player, with a premium provider supplied via BYOK for the voices. ## Next steps * [Installation](/getting-started/installation/) — add the script or install from npm. * [Voice Selection](/guides/voice-selection/) — filter and resolve voices. * [REST API](/rest-api/) — server-side synthesis. # WordPress Plugin > Add text-to-speech to WordPress posts and pages with the ResponsiveVoice plugin — the v2 WebPlayer, classic Listen buttons, and a Gutenberg block. ![ResponsiveVoice for WordPress — "Give your pages a voice", the text-to-speech toolkit for WordPress, showing the WebPlayer control and a v2 badge on a purple gradient.](/img/wordpress/banner.png) The [**ResponsiveVoice Text To Speech**](https://wordpress.org/plugins/responsivevoice-text-to-speech/) plugin adds spoken audio to WordPress posts and pages. There are two ways to use it: * **The WebPlayer** — an automatic floating audio player that reads a post aloud, with paragraph highlighting and playback controls. No per-post markup required. * **Listen buttons** — classic “read this” buttons you place with a shortcode or a Gutenberg block. With a free API key, both speak in **100+ voices across 50+ languages**. They work on smartphone, tablet, and desktop, with nothing extra to install on your server. ## Requirements | Requirement | Minimum | | ----------- | -------------- | | WordPress | 6.3 | | PHP | 7.4 | | License | GPLv2 or later | An API key is optional but recommended — without one the plugin runs in a limited [demo mode](#get-an-api-key). ## Install and activate 1. Install **ResponsiveVoice Text To Speech** from the [WordPress plugin directory](https://wordpress.org/plugins/responsivevoice-text-to-speech/), or upload the plugin ZIP under **Plugins → Add New → Upload Plugin**. 2. Activate it through the **Plugins** menu. 3. Open the **ResponsiveVoice** admin page from the WordPress sidebar. ## Get an API key Your API key is a **public website identifier**, not a secret — it names your site to the ResponsiveVoice service so the right voice catalog and configuration load. 1. [Register for a free account](https://responsivevoice.org/register). 2. Copy your API key from the [ResponsiveVoice dashboard](https://app.responsivevoice.org). 3. Paste it into the **ResponsiveVoice** admin page in WordPress. Demo mode With no API key the listen buttons and shortcodes still work, speaking with the browser’s built-in voice, and the plugin’s admin page shows a prompt to register. Adding a free API key unlocks the full voice catalog and the WebPlayer once your domain is verified — its controls appear on the settings page as soon as the key is in place. ![The ResponsiveVoice settings page without an API key: a "Get started" prompt with a Register your website button above the empty API Key field.](/img/wordpress/settings-demo-mode.png) ## Verify your website With an API key set, text to speech activates once your site’s domain is verified in the [ResponsiveVoice dashboard](https://app.responsivevoice.org) — on an unverified site the listen buttons and the WebPlayer stay silent. Verification is detected in the visitor’s browser — no secret is stored in WordPress. Until your domain is verified, the plugin shows a **“verify your website”** prompt in place of the player, and wp-admin shows a dashboard notice and admin-bar reminder. ![The WordPress dashboard with a ResponsiveVoice warning notice and admin-bar badge prompting to verify the website's domain.](/img/wordpress/verify-cta-notice.png) ## The WebPlayer The WebPlayer is a drop-in article reader — paragraph highlighting, click-to-jump, playback controls, and a floating mini-player that follows the reader once the main controls scroll out of view. Try it live: the [Web Player example](https://examples.responsivevoice.org/browser/web-player/) shows the full player in action. ![A WordPress page on the front end with the WebPlayer pill control sitting above the content, ready to read the page aloud.](/img/wordpress/webplayer-reading-post.png) ### Enable modes The single **ResponsiveVoice** settings page holds your API key and a **WebPlayer** control at the top, with a collapsed **Advanced** section below. The WebPlayer control is a segmented switch: * **Account default** — follow your ResponsiveVoice account setting. * **Enabled** — show the WebPlayer on this site. * **Disabled** — hide the WebPlayer on this site. ### Advanced delivery The **Advanced** section also controls how the ResponsiveVoice library is delivered: from `cdn.responsivevoice.org` (default, always current), pinned to an exact version, or **bundled** — served from your own site, for CSP-strict setups. ### Choose where it shows The WebPlayer switch sets the site-wide default; two finer levels narrow it down: * **Per content type** — the **Show the WebPlayer on** setting toggles it for posts, pages, and custom content types. * **Per post** — a **ResponsiveVoice WebPlayer** panel in the editor sidebar overrides the default for a single post: **Use default**, **Enabled**, or **Disabled**. ![The WordPress page editor sidebar with a ResponsiveVoice WebPlayer panel offering a per-page override dropdown set to Use default.](/img/wordpress/per-post-override.png) ### Customizer The admin page includes a live **customizer** with an instant preview. It surfaces a curated set of the player’s options — the rest come from your dashboard configuration, which the plugin’s settings layer over: * **Theme** — presets plus custom colors. * **Layout** — how the player sits relative to the content. * **Controls** — which buttons and readouts appear. * **Voice** — the default voice for the player. * **Position** — where the player is placed; it aligns to your theme’s content column. ![The ResponsiveVoice settings page in wp-admin: API key, WebPlayer switches, per-content-type toggles, and the customizer with a live preview of the player.](/img/wordpress/settings-customizer.png) For a re-themed player in action, see the [customization example](https://examples.responsivevoice.org/browser/web-player-customization/). For everything the player can do beyond these, see the [Web Player guide](/guides/web-player/) and [dashboard configuration](/guides/app-features/#web-player). ## Listen buttons Prefer a tap-to-listen button over an automatic player? Place one anywhere with a shortcode or the Gutenberg block. Buttons speak with your account’s default voice unless you set the `voice` attribute. Output is CSP-clean — the buttons carry `data-*` attributes with no inline JavaScript. ### Shortcodes * Read the whole post Adds a button that reads the entire post or page. ```text [responsivevoice_button] [responsivevoice_button voice="US English Female" buttontext="Play"] ``` Aliases: `[ListenToPostButton]`, `[RVListenButton]`. Defaults: `buttontext="Listen to this"`. * Read a section Wraps a passage; only the enclosed text is read. ```text [responsivevoice]Text you want read aloud[/responsivevoice] [responsivevoice buttonposition="after"]Text read aloud, button after[/responsivevoice] ``` Alias: `[ResponsiveVoice]`. Defaults: `buttontext="Play"`, `buttonposition="before"`. #### Shortcode attributes | Attribute | Applies to | Description | | ---------------- | --------------- | ----------------------------------------- | | `voice` | all | Voice name, e.g. `US English Female`. | | `buttontext` | button, section | Label shown on the button. | | `buttonposition` | section | `before` (default) or `after` the text. | | `class` | button, section | Extra CSS classes on the rendered button. | | `rate` | button, section | Speech rate. | | `pitch` | button, section | Speech pitch. | | `volume` | button, section | Speech volume. | Note `rate`, `pitch`, and `volume` are optional and take a numeric value; leave them off to use the default. Their ranges are those of the underlying ResponsiveVoice library. Voice names come from the same catalog as the rest of ResponsiveVoice — browse it in the [Gutenberg block’s](#gutenberg-block) **Voice** picker or the [customizer’s](#customizer) **Voice** dropdown, or call `responsiveVoice.getVoices()`. See [Voice Selection](/guides/voice-selection/) for how fallback works and the broader speech options. ### Gutenberg block In the block editor, add the **Listen Button** block (`rvtts/listen-button`) — a server-rendered listen button. Its settings panel offers: * **Voice** — a live picker of your account’s voice catalog, with a **Default (account voice)** option. * **Preview voice** — hear the selected voice without leaving the editor. * **Button text** — the button label. * **Playback** — rate, pitch, and volume sliders. The block also supports WordPress’s native styling controls — color (including gradients), typography, spacing, and border. ![The Listen Button block in the Gutenberg editor: the block settings sidebar shows a voice picker with a Default (account voice) option, a Preview voice button, and a Button text field.](/img/wordpress/block-voice-picker.png) ## Languages and voices The full catalog — **100+ voices across 50+ languages** — is resolved through the ResponsiveVoice [voice resolution chain](/guides/voice-selection/) (native Web Speech where available, server fallback otherwise). Bring-Your-Own-Key providers add [premium neural voices](#can-i-use-premium-neural-voices) on top. The catalog is fetched and cached at runtime, so it improves without updating the plugin. Browse the current list in the [Gutenberg block’s](#gutenberg-block) **Voice** picker or the [customizer’s](#customizer) **Voice** dropdown — both always reflect your account’s catalog — or call `responsiveVoice.getVoices()`. ## Upgrading from 1.7.x Version 2.0 upgrades in place — your API key, settings, and existing shortcodes carry over with no changes required. * All 1.x shortcode tags and attributes keep working. * Buttons now use a neutral style with the ResponsiveVoice icon; restyle them with the `class` attribute or your own CSS. * The `[responsivevoice_box]` voicebox shortcode (and its aliases) was removed. * v1 (legacy) accounts keep Listen buttons and shortcodes in full — the WebPlayer needs a v2 account (see [the FAQ](#im-on-a-v1-legacy-account--what-do-i-get)). ## FAQ ### Do I need an API key? No, but you’ll want one. Without a key, the listen buttons speak with the browser’s built-in voice and the WebPlayer is unavailable. A free API key unlocks the full voice catalog — delivered through a fast text-to-speech API for high-quality voices — plus the WebPlayer; both activate once your domain is [verified](#verify-your-website). It also opens the door to audio streaming and premium neural voices (Google Cloud, Microsoft Azure, OpenAI) via [Bring Your Own Key](#can-i-use-premium-neural-voices). The key is a public website identifier, not a secret. ### Why isn’t the WebPlayer playing? First make sure an API key is set — the WebPlayer’s controls appear on the settings page only once a key is in place. Then check that your domain is [verified](#verify-your-website) — an unverified site silences all text to speech, listen buttons included. If both are in order, one of the [three visibility levels](#choose-where-it-shows) may be hiding it. ### I upgraded from 1.7.x — will my existing shortcodes keep working? Yes — all 1.x shortcode tags and attributes keep working, and your API key and settings carry over. See [Upgrading from 1.7.x](#upgrading-from-17x) for the two visible changes. ### How do I show the WebPlayer only on some content? Three levels — the site-wide switch, per-content-type toggles, and a per-post override in the editor. Details in [Choose where it shows](#choose-where-it-shows). ### Can I control where the player appears on the page? Yes — the customizer’s **Position** control places it before, after, or inside your content, aligned to your theme’s content column, or attaches it to any CSS selector you choose. For everything beyond the customizer, see the [Web Player guide](/guides/web-player/). ### I’m on a v1 (legacy) account — what do I get? Listen buttons and shortcodes work fully. The WebPlayer needs a v2 account — the settings page offers a reversible **Try the v2 WebPlayer** preview, and upgrading is free. ### Can I use premium neural voices? Yes — on ResponsiveVoice v2, Bring Your Own Key (BYOK) connects premium providers (Google Cloud, Microsoft Azure, OpenAI and more) through your dashboard, adding thousands of neural voices to your catalog. Voices you add appear in the plugin automatically — in the block’s voice picker and the WebPlayer — with no plugin configuration. ### Does it work with page builders and the Classic editor? Yes — the shortcodes work in any editor or page builder that renders WordPress shortcodes (the Classic editor, Elementor, and the like), and the WebPlayer attaches to your rendered content no matter what built it. The Listen Button block needs the block editor. More editor integrations are planned. ### What data is sent to ResponsiveVoice? When a visitor plays audio, the text to read and your site’s public API key are sent to the ResponsiveVoice API from their browser. Full detail in [External services and privacy](#external-services-and-privacy). ### Will the plugin slow my site down? It’s built not to — its scripts load at the end of the page and don’t block rendering, and speech is synthesized only when a visitor presses play. Sites with strict CSP or asset policies can switch to bundled delivery or pin a library version — see [Advanced delivery](#advanced-delivery). ## External services and privacy This plugin relies on the ResponsiveVoice service to synthesize speech, so text and your API key are sent to it from visitors’ browsers. * The ResponsiveVoice SDK loads from `cdn.responsivevoice.org`, or, in **bundled** delivery mode, is served from your own site. * When a visitor plays audio, the **text to read** and your site’s **public API key** are sent to the ResponsiveVoice Text-To-Speech API to generate audio. The plugin also reads your site configuration from the API to enable the WebPlayer. See the [Terms of Service](https://responsivevoice.org/terms/) and [Privacy Policy](https://responsivevoice.org/privacy-policy/). ## Support and links [ResponsiveVoice Text To Speech on WordPress.org](https://wordpress.org/plugins/responsivevoice-text-to-speech/) [Register for a free API key](https://responsivevoice.org/register) [ResponsiveVoice dashboard](https://app.responsivevoice.org) [Web Player guide](/guides/web-player/) [Voice Selection](/guides/voice-selection/) [Support](https://responsivevoice.org/support) # REST API > Introduction to the ResponsiveVoice REST API The ResponsiveVoice REST API generates speech audio from any language or platform — no website required. Use it for: * Server-side audio generation * Mobile apps (Android, iOS) and other non-browser clients * Batch processing of text to speech * Platforms without Web Speech API support **Base URL:** `https://texttospeech.responsivevoice.org/v2` Prefer a client library A client library wraps these endpoints with typing, retries, and streaming. For TypeScript or Node, use [`@responsivevoice/api-client`](/sdks/typescript/). From a language without a client library, call the REST endpoints directly as documented below. Tip Building a website rather than calling the API directly? Embed [`@responsivevoice/core`](/getting-started/quick-start/) instead — it speaks via the browser’s Web Speech API and only falls back to this REST API when needed. ## Features * **Multilingual built-in voice catalog** * **Streaming support** via HTTP audio streaming and WebSocket streaming * **BYOK premium providers** — bring your own provider API keys for premium voices * **CDN-cacheable** GET endpoint for efficient audio delivery * **HATEOAS navigation** links in voice responses ## Interactive Documentation Explore the API interactively with the [Scalar API Reference](https://texttospeech.responsivevoice.org/reference). The full OpenAPI specification is available at [`/openapi.json`](https://texttospeech.responsivevoice.org/openapi.json) — import it into Postman, Insomnia, or other API tools. ## Endpoint Reference Detailed endpoint documentation is auto-generated from the OpenAPI specification — see the [Endpoint Reference](/rest-api/reference/). V1 Deprecation V1 is deprecated. Only `GET /v1/text:synthesize` remains for backward compatibility with the legacy JS client. All new integrations should use the v2 endpoints documented in the [Endpoint Reference](/rest-api/reference/). # Authentication > API key and secret authentication for the ResponsiveVoice REST API ## Authentication Every REST API request is authenticated with two credentials sent as headers: * **`X-API-Key`** — your website’s public identifier. * **`X-API-Secret`** — your non-public server credential. Both must be sent together: ```bash curl https://texttospeech.responsivevoice.org/v2/voices \ -H "X-API-Key: YOUR_API_KEY" \ -H "X-API-Secret: YOUR_API_SECRET" ``` ### Getting your credentials Both live in your website’s settings in the [ResponsiveVoice dashboard](https://app.responsivevoice.org) (no account yet? [sign up](https://responsivevoice.org/register)): * **API key** — shown in the “Your site code” snippet (`key: XXXXX`). * **API secret** — created under “Server-to-server API secrets”. It is shown **only once** when generated, so copy it immediately; you can revoke it there at any time. Caution The API secret is a credential — keep it server-side. Never embed it in browser code or commit it to source control. Browser apps use the [`@responsivevoice/core`](/getting-started/quick-start/) SDK, which authenticates by origin and does not need the secret. # Error Handling > Error responses and rate limits for the ResponsiveVoice REST API ## Error Responses All errors return JSON with an `error` object. Validation errors add an `errors` array detailing each problem: ```json { "error": { "message": "Language code is required", "code": "VALIDATION_ERROR", "statusCode": 400, "errors": ["Provide \"lang\" or \"voice\" parameter"] } } ``` ### Error Codes | HTTP Code | Code | Description | | --------- | ---------------------------- | -------------------------------------------------------------------------------------- | | 400 | VALIDATION\_ERROR | Invalid request parameters | | 401 | UNAUTHORIZED | Invalid or missing API key or secret | | 404 | NOT\_FOUND | Voice or resource not found | | 429 | RATE\_LIMIT\_EXCEEDED | Tier request-rate limit exceeded | | 429 | BURST\_RATE\_LIMIT\_EXCEEDED | Too many requests from one client in a short window | | 500 | INTERNAL\_ERROR | Server error | | 502 | *(none)* | Upstream TTS provider error; `message` carries the provider’s failure, no `code` field | ## Rate Limits Rate limits apply per API key, which is required — requests without a valid key are rejected. The same limits apply to both the v1 and v2 APIs. | Plan | Requests per minute | | ---------- | ------------------- | | Free | 100 | | Commercial | 1,000 | | Enterprise | Custom | The per-minute limits above are counted per API key. A separate burst check applies per API key **and** client IP over a short window, so unusually rapid bursts from one client can be limited briefly even while you’re under the per-minute limit. ### Monthly character quota | Plan | Characters per month | | ---------- | ------------------------------------- | | Free | 1,000,000 | | Commercial | Fair use — contact us for high volume | | Enterprise | Custom | Keys that exceed the monthly quota are suspended until upgraded. Note A `429` carries a `Retry-After` header — the seconds to wait before retrying (dynamic; read and honor it, and the retry will succeed). The body `code` says which limit was hit: `RATE_LIMIT_EXCEEDED` (per-minute tier limit) or `BURST_RATE_LIMIT_EXCEEDED` (per API key + client IP burst). `X-RateLimit-Limit` and `X-RateLimit-Remaining` accompany responses and reflect the per-minute limit, so a burst `429` may show remaining requests alongside a short `Retry-After`. # SDKs > Client libraries for the ResponsiveVoice REST API The REST API is plain HTTP and JSON, so any language that can make a request can use it. These clients are for when you would rather not hand-roll one. [TypeScript](/sdks/typescript/)@responsivevoice/api-client — typed requests and responses, retries, and WebSocket streaming. Runs on Node.js and in the browser. [PHP](/sdks/php/)responsivevoice/sdk — synthesis, voices, and configuration from any PHP server, with a Laravel integration example. ## Calling the API directly [REST API](/rest-api/)The endpoints, request shapes, and response formats. [Authentication](/rest-api/authentication/)How to authenticate a request from a server or a browser. ## Speaking in the browser If your target is a web page rather than a server, you probably do not want a REST client at all. [`@responsivevoice/core`](/api/core/src/) speaks through the browser’s own voices and only falls back to the API when it has to — fewer requests, no round trip for most sentences. # PHP SDK > Using the PHP SDK for ResponsiveVoice text-to-speech Official PHP client for the ResponsiveVoice Text-to-Speech API. Source: [`sdk-php`](https://github.com/responsivevoice/sdk-php). ## Installation ```bash composer require responsivevoice/sdk ``` ## Quick Start ```php setApiKey('X-API-Key', getenv('RESPONSIVEVOICE_API_KEY')) ->setApiKey('X-API-Secret', getenv('RESPONSIVEVOICE_API_SECRET')); // Synthesize speech — returns raw MP3 bytes. $audio = (new SynthesisApi(new Client(), $config)) ->v2TextSynthesizeGet('Hello world', 'UK English Female'); file_put_contents('hello.mp3', $audio); // List voices, optionally filtered by language. $voices = (new VoicesApi(new Client(), $config))->v2VoicesGet()->getVoices(); ``` ## Authentication Server-side callers send an API key + secret pair as `X-API-Key` + `X-API-Secret` headers. Get both from your website’s settings in the [ResponsiveVoice dashboard](https://app.responsivevoice.org) ([sign up](https://responsivevoice.org/register)): the **API key** from the “Your site code” snippet, and the **API secret** under “Server-to-server API secrets” (shown only once, revocable). PHP runs server-side, which is exactly where the secret belongs — browser apps use [`@responsivevoice/core`](/getting-started/quick-start/), which authenticates by origin and needs no secret. ## Reference Documentation * [Package README on GitHub](https://github.com/responsivevoice/sdk-php#readme) — full walkthrough, error handling, and the voice/synthesis surface * [Runnable examples](https://github.com/responsivevoice/sdk-php/tree/main/examples) — standalone PHP and a Laravel integration * [REST API reference](/rest-api/) — the underlying HTTP endpoints, for any language Tip Requires PHP 8.2+. For help: . # TypeScript SDK > Using @responsivevoice/api-client for typed TTS API access Official TypeScript REST client with full typing, retry logic, WebSocket streaming, and Node.js/browser support. Source: [`api-client`](https://github.com/responsivevoice/api-client). ## Installation ```bash npm install @responsivevoice/api-client ``` ## Quick Start ```typescript import { ResponsiveVoiceAPIClient } from '@responsivevoice/api-client'; const client = new ResponsiveVoiceAPIClient({ apiKey: process.env.RESPONSIVEVOICE_API_KEY, apiSecret: process.env.RESPONSIVEVOICE_API_SECRET, }); // Synthesize const audio = await client.synthesize({ text: 'Hello world', voice: 'UK English Female', format: 'mp3', }); console.log(audio.blob, audio.url, audio.format); // List voices (with optional filters) const { voices } = await client.getVoices({ lang: 'en-GB', gender: 'female' }); // Cancel a request const controller = new AbortController(); setTimeout(() => controller.abort(), 5_000); await client.synthesize( { text: 'Long text…', voice: 'UK English Female' }, { signal: controller.signal }, ); ``` ## Authentication Server-side callers send an `apiKey` + `apiSecret` pair (the client attaches them as `X-API-Key` + `X-API-Secret`). Get both from your website’s settings in the [ResponsiveVoice dashboard](https://app.responsivevoice.org) ([sign up](https://responsivevoice.org/register)): the **API key** from the “Your site code” snippet, and the **API secret** under “Server-to-server API secrets” (shown only once, revocable). Keep the secret server-side — browser apps use [`@responsivevoice/core`](/getting-started/quick-start/), which authenticates by origin and needs no secret. ## Runtime Notes * **Node 18+**: works out of the box (native `fetch`, `Blob`, `AbortController`). * **Node 16–17**: pass a `fetch` polyfill via the `fetch` config option. * **Node < 22 + WebSocket streaming**: pass a `WebSocket` implementation (e.g. `ws`) to `WebSocketConnection`. ## Reference Documentation * [API Reference (TypeDoc)](/api/api-client/src/) — complete typed API surface * [Package README on GitHub](https://github.com/responsivevoice/api-client#readme) — full walkthrough, all configuration options, error taxonomy, retry tuning Tip The client defaults to the v2 API base URL. For help: .