On-device speech transcription for Expo apps. Supports iOS (Apple Speech framework) and Android (SpeechRecognizer API).
- 🎯 On-device transcription - Works offline, privacy-focused
- 📱 Cross-platform - iOS 13+ and Android 13+ (API 33)
- 🚀 Multiple APIs - SFSpeechRecognizer (iOS 13+), SpeechAnalyzer (iOS 26+), and Android SpeechRecognizer
- 📦 Easy integration - Auto-configures permissions
- 🔒 Secure - All processing happens on device
- ⚡ Realtime transcription - Get live speech-to-text updates with built-in audio capture
- 📁 File transcription - Transcribe pre-recorded audio files
- 🎤 Buffer-based transcription - Stream audio buffers from external sources for real-time transcription
npx expo install expo-speech-transcriber expo-audioAdd the plugin to your app.json:
{
"expo": {
"plugins": ["expo-audio", "expo-speech-transcriber"]
}
}Apple requires a clear purpose string for speech recognition and microphone permissions. Without it, your app may be rejected during App Store review. Provide a descriptive message explaining why your app needs access.
{
"expo": {
"plugins": [
"expo-audio",
[
"expo-speech-transcriber",
{
"speechRecognitionPermission": "We need speech recognition to transcribe your recordings",
"microphonePermission": "We need microphone access to record audio for transcription"
}
]
]
}
}For more details, see Apple's guidelines on requesting access to protected resources.
Note for Android: The plugin automatically adds the
RECORD_AUDIOpermission to your Android manifest. No additional configuration is required.
Start transcribing speech in real-time. This does not require expo-audio.
import { Platform } from "react-native";
import * as SpeechTranscriber from "expo-speech-transcriber";
// Request permissions
// Note: requestPermissions() is only needed on iOS
if (Platform.OS === "ios") {
const speechPermission = await SpeechTranscriber.requestPermissions();
if (speechPermission !== "authorized") {
console.log("Speech permission denied");
return;
}
}
const micPermission = await SpeechTranscriber.requestMicrophonePermissions();
if (micPermission !== "granted") {
console.log("Microphone permission denied");
return;
}
// Use the hook for realtime updates
const { text, isFinal, error, isRecording } =
SpeechTranscriber.useRealTimeTranscription();
// Start transcription
await SpeechTranscriber.recordRealTimeAndTranscribe();
// Stop when done
SpeechTranscriber.stopListening();NOTE: See RecordRealTimeAndTrancribe for an example on how to use Real Time transcription on android.
Transcribe pre-recorded audio files. Our library handles transcription but not recording—use expo-audio to record audio (see expo-audio documentation), or implement your own recording logic with microphone access via requestMicrophonePermissions().
import * as SpeechTranscriber from "expo-speech-transcriber";
import { useAudioRecorder, RecordingPresets } from "expo-audio";
// Record audio with expo-audio
const audioRecorder = useAudioRecorder(RecordingPresets.HIGH_QUALITY);
await audioRecorder.prepareToRecordAsync();
audioRecorder.record();
// ... user speaks ...
await audioRecorder.stop();
const audioUri = audioRecorder.uri;
// Transcribe with SFSpeechRecognizer (preferred)
const text = await SpeechTranscriber.transcribeAudioWithSFRecognizer(audioUri);
console.log("Transcription:", text);
// Or with SpeechAnalyzer if available
if (SpeechTranscriber.isAnalyzerAvailable()) {
const text = await SpeechTranscriber.transcribeAudioWithAnalyzer(audioUri);
console.log("Transcription:", text);
}For custom recording without expo-audio:
// Request microphone permission for your custom recording implementation
const micPermission = await SpeechTranscriber.requestMicrophonePermissions();
// Implement your own audio recording logic here to save a file
// Then transcribe the resulting audio file URIStream audio buffers directly to the transcriber for real-time processing. This is ideal for integrating with audio processing libraries like react-native-audio-api.
import * as SpeechTranscriber from "expo-speech-transcriber";
import { AudioManager, AudioRecorder } from "react-native-audio-api";
// Set up audio recorder
const recorder = new AudioRecorder({
sampleRate: 16000,
bufferLengthInSamples: 1600,
});
AudioManager.setAudioSessionOptions({
iosCategory: "playAndRecord",
iosMode: "spokenAudio",
iosOptions: ["allowBluetooth", "defaultToSpeaker"],
});
// Request permissions
const speechPermission = await SpeechTranscriber.requestPermissions();
const micPermission = await AudioManager.requestRecordingPermissions();
// Stream audio buffers to transcriber
recorder.onAudioReady(({ buffer }) => {
const channelData = buffer.getChannelData(0);
SpeechTranscriber.realtimeBufferTranscribe(
channelData, // Float32Array or number[]
16000, // sample rate
);
});
// Use the hook to get transcription updates
const { text, isFinal, error } = SpeechTranscriber.useRealTimeTranscription();
// Start streaming
recorder.start();
// Stop when done
recorder.stop();
SpeechTranscriber.stopBufferTranscription();See the BufferTranscriptionExample for a complete implementation.
Request speech recognition permission.
Platform: iOS only. On Android, speech recognition permission is handled through requestMicrophonePermissions().
Returns: Promise<PermissionTypes> - One of: 'authorized', 'denied', 'restricted', or 'notDetermined'
Example:
import { Platform } from "react-native";
if (Platform.OS === "ios") {
const status = await SpeechTranscriber.requestPermissions();
}Request microphone permission.
Returns: Promise<MicrophonePermissionTypes> - One of: 'granted' or 'denied'
Example:
const status = await SpeechTranscriber.requestMicrophonePermissions();Start real-time speech transcription. Listen for events via useRealTimeTranscription hook.
Returns: Promise<void>
Example:
await SpeechTranscriber.recordRealTimeAndTranscribe();Stop real-time transcription.
Returns: void
Example:
SpeechTranscriber.stopListening();Check if real-time transcription is currently recording.
Returns: boolean
Example:
const recording = SpeechTranscriber.isRecording();Transcribe audio from a pre-recorded file using SFSpeechRecognizer. I prefer this API for its reliability.
Platform: iOS only
Requires: iOS 13+, pre-recorded audio file URI (record with expo-audio or your own implementation)
Returns: Promise<string> - Transcribed text
Example:
const transcription = await SpeechTranscriber.transcribeAudioWithSFRecognizer(
"file://path/to/audio.m4a"
);Transcribe audio from a pre-recorded file using SpeechAnalyzer.
Platform: iOS only
Requires: iOS 26+, pre-recorded audio file URI (record with expo-audio or your own implementation)
Returns: Promise<string> - Transcribed text
Example:
const transcription = await SpeechTranscriber.transcribeAudioWithAnalyzer(
"file://path/to/audio.m4a"
);Check if SpeechAnalyzer API is available.
Platform: iOS only. Always returns false on Android.
Returns: boolean - true if iOS 26+, false otherwise
Example:
if (SpeechTranscriber.isAnalyzerAvailable()) {
// Use SpeechAnalyzer
}React hook for real-time transcription state.
Returns: { text: string, isFinal: boolean, error: string | null, isRecording: boolean }
Example:
const { text, isFinal, error, isRecording } =
SpeechTranscriber.useRealTimeTranscription();Stream audio buffers for real-time transcription. Ideal for integration with audio processing libraries.
Parameters:
buffer: Float32Array | number[]- Audio samplessampleRate: number- Sample rate in Hz (e.g., 16000)
NOTE We currently support transcription for mono audio only. Natively, the channel is set to 1.
Returns: Promise<void>
Example:
const audioBuffer = new Float32Array([...]);
await SpeechTranscriber.realtimeBufferTranscribe(audioBuffer, 16000);Stop buffer-based transcription and clean up resources.
Returns: void
Example:
SpeechTranscriber.stopBufferTranscription();See the example app for a complete implementation demonstrating all APIs.
- iOS 13.0+
- Expo SDK 52+
- Development build (Expo Go not supported - why?)
- Android 13+ (API level 33)
- Expo SDK 52+
- Development build (Expo Go not supported)
- English only - Currently hardcoded to
en_USlocale - File size - Best for short recordings (< 1 minute)
- Recording not included - Real-time transcription captures audio internally; file transcription requires pre-recorded audio files (use
expo-audioor implement your own recording withrequestMicrophonePermissions()) - Android file transcription - File-based transcription (
transcribeAudioWithSFRecognizer,transcribeAudioWithAnalyzer) is iOS only. Android supports real-time transcription - Android API level - Android requires API level 33+ (Android 13)
MIT
Contributions welcome! Please open an issue or PR on GitHub.