A powerful, easy-to-use React Native library for real-time speech-to-text conversion. Built with the New Architecture (Turbo Modules) for optimal performance on both iOS and Android.
- 🎤 Real-time transcription with partial results as you speak
- 📱 Cross-platform support for iOS and Android
- 🎯 Confidence scores for transcription accuracy
- 🌍 Multi-language support
- ⚡ Event-driven architecture with listeners
- 🔒 Built-in permission handling
- 🏗️ New Architecture ready (Turbo Modules)
- 📝 TypeScript definitions included
- ⚙️ Expo Config Plugin for automatic setup
| iOS | Android |
|---|---|
android-preview.mov |
ios-preview.MP4 |
npm install @dbkable/react-native-speech-to-textor
yarn add @dbkable/react-native-speech-to-textAdd the plugin to your app.json or app.config.js:
{
"expo": {
"plugins": ["@dbkable/react-native-speech-to-text"]
}
}That's it! The plugin automatically configures permissions for both iOS and Android. Run npx expo prebuild to apply the changes.
You can customize the iOS permission messages:
{
"expo": {
"plugins": [
[
"@dbkable/react-native-speech-to-text",
{
"microphonePermission": "Allow $(PRODUCT_NAME) to access your microphone",
"speechRecognitionPermission": "Allow $(PRODUCT_NAME) to recognize your speech"
}
]
]
}
}Install pods:
cd ios && pod installAdd the following to your Info.plist:
<key>NSSpeechRecognitionUsageDescription</key>
<string>This app needs speech recognition to convert your voice to text</string>
<key>NSMicrophoneUsageDescription</key>
<string>This app needs microphone access to record your voice</string>Add the following permission to your AndroidManifest.xml:
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<uses-permission android:name="android.permission.RECORD_AUDIO" />
</manifest>The package handles runtime permission requests automatically.
import { useState, useEffect } from 'react';
import {
start,
stop,
requestPermissions,
isAvailable,
addSpeechResultListener,
addSpeechErrorListener,
addSpeechEndListener,
type SpeechResult,
} from '@dbkable/react-native-speech-to-text';
export default function App() {
const [transcript, setTranscript] = useState('');
const [isListening, setIsListening] = useState(false);
useEffect(() => {
// Listen for results
const resultListener = addSpeechResultListener((result: SpeechResult) => {
setTranscript(result.transcript);
console.log('Confidence:', result.confidence);
});
// Listen for errors
const errorListener = addSpeechErrorListener((error) => {
console.error('Speech error:', error);
setIsListening(false);
});
// Listen for end of speech
const endListener = addSpeechEndListener(() => {
setIsListening(false);
});
// Cleanup
return () => {
resultListener.remove();
errorListener.remove();
endListener.remove();
};
}, []);
const handleStart = async () => {
try {
const available = await isAvailable();
if (!available) {
alert('Speech recognition not available');
return;
}
const hasPermission = await requestPermissions();
if (!hasPermission) {
alert('Permission denied');
return;
}
await start({ language: 'en-US' });
setIsListening(true);
} catch (error) {
console.error(error);
}
};
const handleStop = async () => {
try {
await stop();
} catch (error) {
console.error(error);
}
};
return (
<View>
<Text>{transcript || 'Press start to begin'}</Text>
<Button
title={isListening ? 'Stop' : 'Start'}
onPress={isListening ? handleStop : handleStart}
/>
</View>
);
}Starts speech recognition.
await start({ language: 'en-US' });Options:
language(string, required): Language code (e.g., "en-US", "fr-FR", "es-ES", "de-DE")
Throws:
PERMISSION_DENIED: User denied permissionsNOT_AVAILABLE: Speech recognition not availableSTART_FAILED: Failed to start recognition
Stops speech recognition and sends the final transcript.
await stop();Requests necessary permissions for speech recognition.
const granted = await requestPermissions({
title: 'Microphone Permission',
message: 'This app needs access to your microphone for speech recognition',
buttonPositive: 'OK',
});Options (Android only):
title(string, optional): Dialog titlemessage(string, optional): Dialog messagebuttonNeutral(string, optional): Neutral button textbuttonNegative(string, optional): Negative button textbuttonPositive(string, optional): Positive button text
Returns: boolean - true if permission granted, false otherwise
Checks if speech recognition is available on the device.
const available = await isAvailable();
if (!available) {
console.log('Speech recognition not supported');
}Returns: boolean
Listens for transcription results (both partial and final).
const listener = addSpeechResultListener((result) => {
console.log('Transcript:', result.transcript);
console.log('Confidence:', result.confidence);
console.log('Is final:', result.isFinal);
});
// Don't forget to remove the listener
listener.remove();SpeechResult:
transcript(string): The recognized textconfidence(number): Confidence score from 0.0 to 1.0isFinal(boolean):truefor final result,falsefor partial
Listens for error events.
const listener = addSpeechErrorListener((error) => {
console.error('Error code:', error.code);
console.error('Error message:', error.message);
});
listener.remove();SpeechError:
code(string): Error code (see Error Codes)message(string): Human-readable error message
Called when speech recognition ends.
const listener = addSpeechEndListener(() => {
console.log('Speech recognition ended');
});
listener.remove();interface SpeechToTextOptions {
language: string; // e.g., "en-US", "fr-FR", "es-ES"
}
interface PermissionOptions {
title?: string;
message?: string;
buttonNeutral?: string;
buttonNegative?: string;
buttonPositive?: string;
}
interface SpeechResult {
transcript: string;
confidence: number;
isFinal: boolean;
}
interface SpeechError {
code: SpeechErrorCode | string;
message: string;
}
enum SpeechErrorCode {
PERMISSION_DENIED = 'PERMISSION_DENIED',
NOT_AVAILABLE = 'NOT_AVAILABLE',
REQUEST_FAILED = 'REQUEST_FAILED',
START_FAILED = 'START_FAILED',
STOP_FAILED = 'STOP_FAILED',
AUDIO_ERROR = 'AUDIO_ERROR',
CLIENT_ERROR = 'CLIENT_ERROR',
NETWORK_ERROR = 'NETWORK_ERROR',
NETWORK_TIMEOUT = 'NETWORK_TIMEOUT',
RECOGNIZER_BUSY = 'RECOGNIZER_BUSY',
SERVER_ERROR = 'SERVER_ERROR',
UNKNOWN_ERROR = 'UNKNOWN_ERROR',
}If you're using Expo, the plugin automatically configures native permissions for you.
{
"expo": {
"plugins": ["@dbkable/react-native-speech-to-text"]
}
}{
"expo": {
"plugins": [
[
"@dbkable/react-native-speech-to-text",
{
"microphonePermission": "Custom message for microphone access",
"speechRecognitionPermission": "Custom message for speech recognition"
}
]
]
}
}| Option | Type | Default | Description |
|---|---|---|---|
microphonePermission |
string | "Allow $(PRODUCT_NAME) to access your microphone to record audio for speech recognition" |
iOS only: Custom message for NSMicrophoneUsageDescription |
speechRecognitionPermission |
string | "Allow $(PRODUCT_NAME) to use speech recognition to convert your voice to text" |
iOS only: Custom message for NSSpeechRecognitionUsageDescription |
The Expo Config Plugin automatically:
- iOS: Adds
NSMicrophoneUsageDescriptionandNSSpeechRecognitionUsageDescriptiontoInfo.plist - Android: Adds
RECORD_AUDIOpermission toAndroidManifest.xml
After adding or modifying the plugin configuration, run:
npx expo prebuildYou can use any standard locale identifier. Here are some examples:
- English:
en-US,en-GB,en-AU - French:
fr-FR,fr-CA - Spanish:
es-ES,es-MX - German:
de-DE - Italian:
it-IT - Portuguese:
pt-BR,pt-PT - Japanese:
ja-JP - Chinese:
zh-CN,zh-TW - Korean:
ko-KR - Arabic:
ar-SA
Availability depends on the device and platform. Use isAvailable() to check.
Expo users: If you've added the plugin to your app.json, permissions should be configured automatically. Make sure to run npx expo prebuild after adding the plugin.
Bare React Native - iOS:
- Make sure you've added
NSSpeechRecognitionUsageDescriptionandNSMicrophoneUsageDescriptionto yourInfo.plist - Check that the user granted permissions in Settings > Your App
Bare React Native - Android:
- Ensure
RECORD_AUDIOpermission is inAndroidManifest.xml - Call
requestPermissions()beforestart()
- iOS: Speech recognition requires iOS 10+ and is not available in the simulator for some iOS versions. Test on a real device.
- Android: Ensure Google app or speech recognition service is installed and up to date.
- Some older devices may not support speech recognition.
- Partial results are enabled by default on both platforms
- On Android, partial results appear after a short delay
- If you're only seeing final results, check that you're handling the
isFinalflag correctly
- iOS: May stop automatically after detecting silence
- Android: Configured with 2-second pause detection and 10-second minimum recording
- Call
start()again to restart recognition
- Speak clearly and in a quiet environment
- Ensure the device microphone is not obstructed
- Try a different language/locale that better matches the speaker's accent
Some speech recognition services require internet connectivity:
- iOS: On-device recognition available on iOS 13+ for some languages
- Android: Depends on the device's speech recognition provider
Ensure the device has internet access for best results.
We welcome contributions! Here's how to get started:
- Fork the repository on GitHub
- Clone your fork locally
- Create a branch for your feature:
git checkout -b my-feature - Make your changes and add tests if applicable
- Test thoroughly on both iOS and Android
- Commit your changes:
git commit -am 'feat: add amazing feature'(follow Conventional Commits) - Push to your fork:
git push origin my-feature - Open a Pull Request on GitHub
# Clone the repo
git clone https://github.com/adelbeke/react-native-speech-to-text.git
cd react-native-speech-to-text
# Install dependencies
yarn install
# Run the example app
yarn example ios
# or
yarn example androidThis project uses ESLint and Prettier. Run:
yarn lint
yarn typescriptFor more details, see CONTRIBUTING.md.
MIT © Arthur Delbeke
Built with create-react-native-library
Made with ❤️ for the React Native community