An iOS only on-device transcription library for React Native and Expo apps.
npm install expo-speech-transcriberOn-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
``bash`
npx expo install expo-speech-transcriber expo-audio
Add the plugin to your app.json:
`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.
`json`
{
"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_AUDIO permission to your Android manifest. No additional configuration is required.
Start transcribing speech in real-time. This does not require expo-audio.
`typescript
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().
`typescript
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:
`typescript`
// 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 URI
Stream audio buffers directly to the transcriber for real-time processing. This is ideal for integrating with audio processing libraries like react-native-audio-api.
`typescript
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.
Platform: iOS only. On Android, speech recognition permission is handled through requestMicrophonePermissions().
Returns: Promise - One of: 'authorized', 'denied', 'restricted', or 'notDetermined'
Example:
`typescript
import { Platform } from "react-native";
if (Platform.OS === "ios") {
const status = await SpeechTranscriber.requestPermissions();
}
`
Request microphone permission.
Returns: Promise - One of: 'granted' or 'denied'
Example:
`typescript`
const status = await SpeechTranscriber.requestMicrophonePermissions();
Start real-time speech transcription. Listen for events via useRealTimeTranscription hook.
Returns: Promise
Example:
`typescript`
await SpeechTranscriber.recordRealTimeAndTranscribe();
Stop real-time transcription.
Returns: void
Example:
`typescript`
SpeechTranscriber.stopListening();
Check if real-time transcription is currently recording.
Returns: boolean
Example:
`typescript`
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 - Transcribed text
Example:
`typescript`
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 - Transcribed text
Example:
`typescript`
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:
`typescript`
if (SpeechTranscriber.isAnalyzerAvailable()) {
// Use SpeechAnalyzer
}
React hook for real-time transcription state.
Returns: { text: string, isFinal: boolean, error: string | null, isRecording: boolean }
Example:
`typescript`
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
Example:
`typescript`
const audioBuffer = new Float32Array([...]);
await SpeechTranscriber.realtimeBufferTranscribe(audioBuffer, 16000);
Stop buffer-based transcription and clean up resources.
Returns: void
Example:
`typescript`
SpeechTranscriber.stopBufferTranscription();
See the example app for a complete implementation demonstrating all APIs.
- English only - Currently hardcoded to en_US localeexpo-audio
- 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 or implement your own recording with requestMicrophonePermissions())transcribeAudioWithSFRecognizer
- Android file transcription - File-based transcription (, 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.