[KongchangAI]
· 3 min read· 1,600 words

Add AI Voice Narration to Your WordPress Site with ElevenLabs

Add AI Voice Narration to Your WordPress Site with ElevenLabs

Build a voice-enabled WordPress site with a lightweight ElevenLabs TTS plugin in just a few dozen lines of PHP.

This guide shows how to integrate ElevenLabs' text-to-speech and voice cloning capabilities into WordPress via a lightweight plugin, enabling brand-specific audio narration for any post. The stack has three layers: ElevenLabs handles high-quality synthesis, a PHP plugin calls the API via `wp_remote_post` and caches MP3s locally using MD5 hashes, and the frontend offers two interaction modes — a static `[voice_tts]` shortcode with an HTML5 player, or an on-demand "Listen" button backed by a REST endpoint. The article also covers voice parameter tuning, CDN delivery, secure API key storage, and quota cost management for production environments.

Imagine your website reading articles aloud, answering questions, or narrating product demos in your brand's own voice. With text-to-speech (TTS) and voice cloning technology now mature enough for production use, this is no longer a distant dream — with the ElevenLabs API, you can build it in a matter of hours. This guide walks you through creating a voice-enabled WordPress site using a lightweight plugin that converts post content to audio in real time.

Build a Voice-Enabled Website with ElevenLabs and WordPress

How It Works & Prerequisites

The architecture is straightforward: WordPress handles content management, ElevenLabs handles speech synthesis, and a small plugin bridges the two with just a few dozen lines of code. By the end, you'll have three things — a WordPress plugin that sends any content to ElevenLabs for synthesis, a frontend audio control that plays audio directly on the page, and a live site your visitors can experience.

Before you start, you'll need: a WordPress site (a fresh install works fine — the plugin hooks into WordPress's content pipeline); an ElevenLabs API key for access to high-quality TTS and voice cloning; and a basic understanding of PHP, JavaScript, and cURL. All the tools offer free tiers, and the example code is fully open source, so feel free to adapt it to match your brand.

Verify the ElevenLabs API

Once you have your API key, the recommended first step is to test the endpoint with a curl command rather than jumping straight into plugin development. This quickly confirms your key is valid and your voice ID is correct, saving you from debugging issues later during integration.

curl -X POST "https://api.elevenlabs.io/v1/text-to-speech/voice-id" \
     -H "xi-api-key: YOUR_ELEVENLABS_API_KEY" \
     -H "Content-Type: application/json" \
     -d '{
           "text": "Hello, welcome to our voice-enabled site!",
           "voice_settings": {
               "stability": 0.75,
               "similarity_boost": 0.85
           }
         }' --output hello.mp3

Replace voice-id with the voice ID you want to use (you can create custom cloned voices in the ElevenLabs console). The command returns a hello.mp3 file — play it locally to confirm the result sounds good, then move on to the WordPress integration. Two key parameters are worth noting here: stability controls tonal consistency, and similarity_boost determines how closely the cloned voice matches the original recording. These two values will be your primary levers when fine-tuning the voice later.

You can obtain a voice ID in two ways: by browsing the platform's built-in public voice library (under Voices → VoiceLab in the console, where each entry has a copyable ID string), or by uploading 1–3 minutes of your own recording to create a "cloned voice" — the platform uses deep learning to extract vocal characteristics and generate a unique ID. Voice cloning is available on the Creator plan and above; the free tier is limited to the public voice library. The stability parameter ranges from 0–1: higher values produce more consistent but slightly robotic delivery, while lower values add more emotional variation at the cost of consistency. similarity_boost controls how closely the cloned voice matches the source recording — setting it too high can introduce background noise or audio degradation. A good starting point is stability: 0.75 / similarity_boost: 0.75, then tune incrementally based on results.

Building the WordPress Plugin

Wrapping the functionality in a plugin means you can enable or disable it with one click from the admin dashboard, without touching your theme code. Create a voice-tts folder inside wp-content/plugins and add a voice-tts.php file inside it.

The first part of the plugin registers a [voice_tts] shortcode that outputs an audio player:

<?php
/**
 * Plugin Name: Voice TTS for WordPress
 * Description: Sends post content to ElevenLabs and embeds an audio player.
 * Version: 1.0
 */
if ( ! defined( 'ABSPATH' ) ) { exit; }

function vt_tts_shortcode( $atts, $content = null ) {
    $post_id = get_the_ID();
    $text    = get_post_field( 'post_content', $post_id );
    $audio_url = vt_generate_audio( $text );
    if ( ! $audio_url ) {
        return '<p>Unable to generate audio at this time.</p>';
    }
    return sprintf(
        '<audio controls src="%s" style="width:100%%; margin-top:1rem;"></audio>',
        esc_url( $audio_url )
    );
}
add_shortcode( 'voice_tts', 'vt_tts_shortcode' );

The second part is the helper function that actually communicates with ElevenLabs. It uses WordPress's built-in wp_remote_post to make the request, wp_strip_all_tags to sanitize the post HTML, then saves the returned MP3 to the uploads directory using an MD5 hash of the content as the filename:

function vt_generate_audio( $text ) {
    $api_key  = 'YOUR_ELEVENLABS_API_KEY';
    $voice_id = 'YOUR_VOICE_ID';
    $endpoint = "https://api.elevenlabs.io/v1/text-to-speech/{$voice_id}";
    $payload = [
        'text' => wp_strip_all_tags( $text ),
        'voice_settings' => [ 'stability' => 0.75, 'similarity_boost' => 0.85 ],
    ];
    $args = [
        'body'    => wp_json_encode( $payload ),
        'headers' => [ 'Content-Type' => 'application/json', 'xi-api-key' => $api_key ],
        'timeout' => 30,
    ];
    $response = wp_remote_post( $endpoint, $args );
    if ( is_wp_error( $response ) ) { return false; }
    $upload_dir = wp_upload_dir();
    $filename   = 'voice-' . md5( $text ) . '.mp3';
    $file_path  = trailingslashit( $upload_dir['path'] ) . $filename;
    file_put_contents( $file_path, wp_remote_retrieve_body( $response ) );
    return trailingslashit( $upload_dir['url'] ) . $filename;
}

Using md5($text) as the filename is a clever touch: identical content always produces the same filename, giving you a natural caching layer so the same article never triggers a duplicate API call. Once the file is saved, activate the plugin under Plugins → Installed Plugins, add the [voice_tts] shortcode to any post or page, and the audio player will be generated and embedded on page load.

Hardcoding the API key and Voice ID directly in the plugin file is fine for local development, but not suitable for team environments or version-controlled repos. The recommended approach is to store sensitive config in WordPress's wp_options table: add a settings page with add_options_page() and read/write the key using update_option() / get_option(), keeping credentials out of your codebase. For stricter isolation, define constants in wp-config.php or as server environment variables (e.g., define('ELEVENLABS_API_KEY', getenv('ELEVENLABS_KEY'))), then read them in the plugin with defined(). wp_remote_post is WordPress's abstraction over cURL/HTTP — it automatically handles SSL certificate verification, timeout retries, and other details, making it more idiomatic than calling curl_exec directly and easier to intercept in unit tests via the pre_http_request hook.

Optional: A "Click to Listen" Frontend Button

If you'd rather not attach a static player to every article, and instead want to give visitors an on-demand "Listen to this article" button, you can add a short JavaScript snippet to your theme's footer.php. It dynamically inserts a button at the top of the post content area; when clicked, it grabs the post text, calls a REST endpoint to synthesize the audio, and plays it immediately:

<script>
document.addEventListener('DOMContentLoaded', () => {
    const btn = document.createElement('button');
    btn.textContent = '🔊 Listen to this article';
    document.querySelector('.entry-content').prepend(btn);
    btn.addEventListener('click', async () => {
        const text = document.querySelector('.entry-content').innerText;
        const response = await fetch('/wp-json/voice-tts/v1/generate', {
            method: 'POST',
            headers: { 'Content-Type': 'application/json' },
            body: JSON.stringify({ text })
        });
        const { audioUrl } = await response.json();
        new Audio(audioUrl).play();
    });
});
</script>

To make this work, you also need to expose a REST endpoint in the plugin using register_rest_route, which calls vt_generate_audio() internally and returns the URL as JSON. This interaction pattern makes listening a deliberate visitor choice, improving UX while keeping static players from cluttering every page.

The REST endpoint needs to be registered during plugin initialization via register_rest_route, typically hooked onto the rest_api_init action. A minimal implementation looks like this:

add_action( 'rest_api_init', function() {
    register_rest_route( 'voice-tts/v1', '/generate', [
        'methods'  => 'POST',
        'callback' => function( $request ) {
            $text = sanitize_textarea_field( $request->get_param('text') );
            $url  = vt_generate_audio( $text );
            return $url ? [ 'audioUrl' => $url ] : new WP_Error( 'tts_failed', 'Generation failed', [ 'status' => 500 ] );
        },
        'permission_callback' => '__return_true',
    ]);
});

In production, replace permission_callback with real authorization logic (such as nonce verification or requiring a logged-in user) to prevent the endpoint from being abused and draining your API quota. The frontend fetch request should also include a WordPress-generated nonce token, verified server-side with check_ajax_referer or wp_verify_nonce, providing full CSRF protection.

Testing and Tuning

Once deployed, a few details determine the final quality of the voice experience. On audio load speed: since the plugin caches the MP3 locally after the first request, revisiting the same article will load the player almost instantly. On voice tuning: changing $voice_id in the plugin lets you switch to any voice you've created in ElevenLabs, and tweaking stability and similarity_boost helps you dial in the tone that best fits your brand.

For high-traffic sites, consider serving the generated MP3s through a CDN (such as Cloudflare) to reduce latency — the plugin already writes files to the wp-content/uploads directory, making them straightforward to distribute or cache. If you encounter 403 Forbidden errors, start by checking that your API key is correct and that your server allows outbound HTTPS connections.

Where to Go Next

The real value of this stack lies in how lightweight and extensible it is: one powerful voice API, one small plugin, and your site can speak. More importantly, the same pattern extends naturally to other use cases — podcast generation, audio narration for online courses, or even an interactive voice Q&A bot. The recommended approach is to validate the full pipeline with a single article first, then expand incrementally from there.

One important reminder: the example code hardcodes the API key directly in the plugin for demonstration purposes. In production, store credentials using WordPress settings or environment variables to avoid exposure. Also keep in mind that every synthesis call consumes ElevenLabs character quota — for long articles and high-traffic scenarios, make sure you have proper caching in place and a clear sense of the cost implications.

#ElevenLabs WordPress#AI voice narration WordPress#text-to-speech WordPress plugin#ElevenLabs TTS API#voice cloning WordPress#WordPress audio player plugin
Share:

Related articles