# How to Add a New TTS Backend to Speech-to-Speech: Handler Pattern Guide

> Easily add a new TTS backend to Speech-to-Speech. Learn the handler pattern for CLI configuration, handler implementation, and backend registration with our step-by-step guide.

- Repository: [Hugging Face/speech-to-speech](https://github.com/huggingface/speech-to-speech)
- Tags: how-to-guide
- Published: 2026-08-08

---

**To add a new TTS backend to the Speech-to-Speech system, you must create an arguments class for CLI configuration, implement a handler class extending `BaseTTSHandler`, and register both in [`backend_registry.py`](https://github.com/huggingface/speech-to-speech/blob/main/backend_registry.py) using `BackendRegistry.register_tts_backend()`.**

The Hugging Face Speech-to-Speech (STS) repository uses a clean handler pattern that separates configuration from runtime logic. This architecture allows developers to integrate custom text-to-speech engines without modifying the core pipeline. By following the existing patterns found in [`qwen3_tts_handler.py`](https://github.com/huggingface/speech-to-speech/blob/main/qwen3_tts_handler.py) and [`pocket_tts_handler.py`](https://github.com/huggingface/speech-to-speech/blob/main/pocket_tts_handler.py), you can add a new TTS backend that automatically inherits CLI argument parsing, error handling, and streaming capabilities.

## Step 1: Create the Argument Class for CLI Options

All backend-specific configuration in Speech-to-Speech is encapsulated in argument classes located in `src/speech_to_speech/arguments_classes/`. Your new backend requires a class that inherits from `BaseArguments` and implements two required methods: `add_cli_arguments` and `as_dict`.

The `add_cli_arguments` method attaches your backend's flags to the global argument parser, while `as_dict` serializes the configuration for the handler's consumption.

```python

# src/speech_to_speech/arguments_classes/mytts_arguments.py

from .base_arguments import BaseArguments

class MyTTSArguments(BaseArguments):
    """CLI arguments for the MyTTS backend."""
    
    def __init__(self):
        self.model_name: str = "mytts/model"
        self.voice: str = "default"
        self.speed: float = 1.0

    @classmethod
    def add_cli_arguments(cls, parser):
        """Add arguments to the shared argparse parser."""
        group = parser.add_argument_group("MyTTS")
        group.add_argument("--mytts-model", default="mytts/model",
                           help="Identifier of the MyTTS model to use.")
        group.add_argument("--mytts-voice", default="default",
                           help="Voice name within the model.")
        group.add_argument("--mytts-speed", type=float, default=1.0,
                           help="Playback speed factor (1.0 = normal).")

    def as_dict(self) -> dict:
        """Return a plain-dict representation for the handler."""
        return {
            "model_name": self.model_name,
            "voice": self.voice,
            "speed": self.speed,
        }

```

Placing this file in the `arguments_classes` directory ensures that the `STSClient` can discover and instantiate your configuration when parsing command-line arguments or configuration files.

## Step 2: Implement the TTS Handler Class

The runtime implementation belongs in `src/speech_to_speech/TTS/` and must inherit from `BaseTTSHandler` defined in [`src/speech_to_speech/baseHandler.py`](https://github.com/huggingface/speech-to-speech/blob/main/src/speech_to_speech/baseHandler.py). This base class provides logging infrastructure and standard interfaces that the pipeline expects.

Your handler must implement `generate(self, text: str) -> bytes` for synchronous synthesis. Optionally, implement `async def stream_generate(self, text: str)` for real-time streaming applications.

```python

# src/speech_to_speech/TTS/mytts_handler.py

from ..baseHandler import BaseTTSHandler
from ..arguments_classes.mytts_arguments import MyTTSArguments

class MyTTSHandler(BaseTTSHandler):
    """Handler that talks to the MyTTS service."""
    
    def __init__(self, args: MyTTSArguments):
        super().__init__(args)
        self.client = None

    def _ensure_client(self):
        """Lazy-load the client library on first use."""
        if self.client is None:
            from mytts import MyTTSClient
            self.client = MyTTSClient(
                model=self.args.model_name,
                voice=self.args.voice,
                speed=self.args.speed
            )

    def generate(self, text: str) -> bytes:
        """Synchronous generation returning raw PCM bytes."""
        self._ensure_client()
        audio_np = self.client.synthesize(text)
        return audio_np.tobytes()

    async def stream_generate(self, text: str):
        """Async generator yielding audio chunks."""
        self._ensure_client()
        async for chunk in self.client.synthesize_stream(text):
            yield chunk.tobytes()

```

Notice that the constructor accepts an instance of the arguments class created in Step 1. The registry automatically injects this dependency when instantiating your handler.

## Step 3: Register the Backend in the Registry

Registration connects your symbolic backend name to the implementation classes. Open [`src/speech_to_speech/backend_registry.py`](https://github.com/huggingface/speech-to-speech/blob/main/src/speech_to_speech/backend_registry.py) and add a call to `BackendRegistry.register_tts_backend()` that maps your chosen name to the handler and arguments classes.

```python

# src/speech_to_speech/backend_registry.py

from .arguments_classes.mytts_arguments import MyTTSArguments
from .TTS.mytts_handler import MyTTSHandler

# Existing registrations:

# BackendRegistry.register_tts_backend("qwen3", Qwen3TTSHandler, Qwen3TTSArguments)

# New registration:

BackendRegistry.register_tts_backend(
    name="mytts",
    handler_cls=MyTTSHandler,
    arguments_cls=MyTTSArguments,
)

```

The `name` parameter becomes the value users pass to the `--tts-backend` command-line flag. Once registered, the system can instantiate your handler and inject the parsed arguments automatically.

## Optional: Add Dependencies and Testing

If your backend requires external packages, declare them in [`pyproject.toml`](https://github.com/huggingface/speech-to-speech/blob/main/pyproject.toml) under the `[project]` dependencies section. This ensures installation when users run `pip install speech-to-speech`.

Verify your integration by creating a test in `tests/` following the pattern in [`test_qwen3_tts_handler_backend.py`](https://github.com/huggingface/speech-to-speech/blob/main/test_qwen3_tts_handler_backend.py):

```python

# tests/test_mytts_handler_backend.py

from speech_to_speech.backend_registry import BackendRegistry
from speech_to_speech.arguments_classes.mytts_arguments import MyTTSArguments

def test_mytts_handler_is_registered():
    handler_cls, args_cls = BackendRegistry.get_tts_backend("mytts")
    assert handler_cls is not None
    assert args_cls is MyTTSArguments

```

Running `pytest tests/test_mytts_handler_backend.py -v` confirms that the registry correctly resolves your backend name to the appropriate classes.

## Using Your New Backend

After completing the implementation, users can invoke your backend via the CLI:

```bash
python -m speech_to_speech \
  --tts-backend mytts \
  --mytts-model mytts/v1 \
  --mytts-voice "alice" \
  --mytts-speed 1.2 \
  --prompt "Hello, world!"

```

The arguments `--mytts-model`, `--mytts-voice`, and `--mytts-speed` appear automatically in `--help` output due to the `add_cli_arguments` implementation in your arguments class.

## Summary

- **Create an arguments class** in `src/speech_to_speech/arguments_classes/` that inherits from `BaseArguments` and implements `add_cli_arguments` and `as_dict` to expose CLI options.
- **Implement the handler** in `src/speech_to_speech/TTS/` by subclassing `BaseTTSHandler` and providing `generate` for synchronous operation and optionally `stream_generate` for async streaming.
- **Register the pair** in [`src/speech_to_speech/backend_registry.py`](https://github.com/huggingface/speech-to-speech/blob/main/src/speech_to_speech/backend_registry.py) using `BackendRegistry.register_tts_backend(name, HandlerClass, ArgumentsClass)`.
- **Add external dependencies** to [`pyproject.toml`](https://github.com/huggingface/speech-to-speech/blob/main/pyproject.toml) if your TTS engine requires additional packages.
- **Test the registration** by asserting that `BackendRegistry.get_tts_backend()` returns your classes for the registered name.

## Frequently Asked Questions

### What base class should my TTS handler extend?

Your handler must extend `BaseTTSHandler` from [`src/speech_to_speech/baseHandler.py`](https://github.com/huggingface/speech-to-speech/blob/main/src/speech_to_speech/baseHandler.py). This base class provides standardized logging, argument injection, and interface contracts that the Speech-to-Speech pipeline requires for all TTS backends.

### How does the system discover my new backend's command-line arguments?

The `STSClient` discovers arguments through the `add_cli_arguments` class method defined in your arguments class. When you register the backend in [`backend_registry.py`](https://github.com/huggingface/speech-to-speech/blob/main/backend_registry.py), the system knows to call this method on your class during argument parsing, automatically adding your flags to the global parser.

### Can I implement only synchronous generation without streaming?

Yes. You must implement `generate(self, text: str) -> bytes`, which returns complete audio data as bytes. The `stream_generate` method is optional; implement it only if your underlying TTS engine supports chunked audio generation and you want to enable real-time streaming capabilities.

### Where should I place external dependencies for my backend?

Declare any required third-party packages in the `[project]` section of [`pyproject.toml`](https://github.com/huggingface/speech-to-speech/blob/main/pyproject.toml) under the `dependencies` list. This ensures that when users install the `speech-to-speech` package, your backend's requirements are satisfied automatically.