1.8.0 (September 24, 2026)
Expo support is now available on the existing 1.x release line, without changing how framework-less ("bare") React Native applications are set up. Expo applications only need to add a config plugin entry.
The 2.0.0-preview.x releases introduced Expo support while we evaluated the integration through the preview channel. Delivering it on 1.x means no major-version upgrade is required. Development will continue on the 1.x line and the 2.0.0-preview.x releases will no longer be maintained.
Migrating from 2.0.0-preview.x
If you are currently using a 2.0.0-preview.x release, migrate to 1.8.0 to continue receiving updates:
-
Install
@twilio/voice-react-native-sdk@1.8.0from npm. Because1.8.0has a lower semantic version than the preview releases, package managers will not automatically move applications from2.0.0-preview.xto1.8.0. -
If you are using Expo, configure the SDK using the config plugin described below. Existing manual
Info.plist, entitlements, and Google Services configuration that is now handled by the plugin can be removed. -
If you are using bare React Native, we recommend you delete your fork and install
@twilio/voice-react-native-sdk@1.8.0from npm. Bare React Native is supported natively in 1.8.0, and staying on a fork stops you receiving native SDK and security updates.
Features
Expo
-
Expo applications are supported through a config plugin. Add
"@twilio/voice-react-native-sdk"topluginsin your app config and run
expo prebuild. See the Expo setup guide.A development build
is required. Expo Go is not supported, because this library contains native
code that Expo Go does not include.Set
android.googleServicesFilein your app config. Expo installs your
google-services.jsonand applies the Google Services Gradle plugin from it,
and incoming calls will not reach the device without it.Exercised on Expo SDK 52 through 57.
ICE configuration
-
Added support for custom ICE servers and ICE transport policy on outgoing
calls started withVoice.connect, via the newiceServersand
iceTransportPolicyoptions. -
The same two options are supported when accepting an incoming call with
CallInvite.accept.
AudioDevice
- Added
AudioDevice.nativeType, which exposes the audio device type exactly as
reported by the native layer, alongside the well-knownAudioDevice.type.
Changes
-
Updated the native Twilio Voice SDK dependencies.
-
Twilio Voice Android SDK upgraded from
6.7.1to6.10.3. -
Twilio Voice iOS SDK upgraded from
6.13.3to6.13.6.
-
-
Errors raised by native methods are now consistently surfaced as
TwilioErrorssubclasses. Previously this applied to some methods but not
others:Voice.connectandCallInvite.acceptalready constructed a typed
error, while methods such asCall.mute,Call.disconnectand
Voice.getVersionlet the underlying React Native bridge error propagate.This changes what those methods throw. Where the native layer reports a
Twilio error code, the error is the matchingTwilioErrorssubclass and
codecarries that code, as before. Where it reports a failure without a
code, the error is now anInvalidStateError, anInvalidArgumentErroror
anUnexpectedNativeError, andcodeon those three isundefined--
1.7.0 gave the React Native bridge's generic'EUNSPECIFIED'there.Applications that read the bridge's undocumented
error.userInfoproperty,
or that relied oncodebeing present, should readerror.messageand
branch oninstanceofinstead:import { InvalidStateError } from '@twilio/voice-react-native-sdk'; try { await call.mute(true); } catch (error) { if (error instanceof InvalidStateError) { // the call was not in a state that can be muted } }
-
Updated the local TypeScript version used by the library.
Fixes
-
Fixed the Expo SDK version being absent from call insights metadata.
Voicerecords the version natively during construction. That call was
fire-and-forget, so a call, registration or preflight test started shortly
after construction could reach the native layer first. On iOS the native SDK
memoizes publisher metadata on the first insights event, so a miss there
persisted for the rest of the application session.connect,register,
unregister,runPreflightandinitializePushRegistrynow wait for the
version to be recorded. Failing to record it can no longer reject any of
them.Separately, the version was not reported at all for applications running an
over-the-air update, whose manifests nest the app config under
extra.expoClientrather than carryingsdkVersionat the top level.CallInvite.acceptnow waits for it as well, and the config plugin records
the version in the built application -- as Android manifest meta-data and as
an iOSInfo.plistkey -- so that an incoming call on a cold start reports
it too. That path runs before the application's JavaScript has constructed
Voice, so no amount of waiting in JavaScript could have covered it. -
Fixed an unresolved native module surfacing as a
TypeErrornaming a
property rather than an error naming the cause. Importing the SDK into an
application whose native code is missing -- not rebuilt since the dependency
was added,pod installnot run, or running in Expo Go -- now throws an
InvalidStateErrorlisting what to check. React Native raises its own
invariant for this on iOS but not on Android, so on Android the first symptom
came from wherever the module was first read. -
Fixed
new Voice()being able to throw. Recording the Expo SDK version is
telemetry and is documented as never preventing a call, registration or
preflight test, but a synchronous failure from the native binding propagated
out of the constructor rather than being swallowed.
Platform Specific Fixes
Android
-
Fixed
Call.holdandCall.mutereturning an incorrect value. -
Fixed
CallInvite.sendMessage. -
Fixed null pointer exception crashes that could occur when accepting or
rejecting invalidCallInvites from the native notification. -
Fixed
AudioDevice.uuidchanging whenever the available audio devices were
re-evaluated, which happens when a device is selected. An application that
selected a device and then comparedselectedDevice.uuidagainst the device
it had selected saw a mismatch even though the correct device was active. A
device now keeps itsuuidfor as long as it remains available. -
Fixed audio device types being misreported in release builds where a code
shrinker had renamed the underlying AudioSwitch classes. -
Fixed
getAudioDevicesdropping a device when two devices of the same type
reported the same name, which two headsets of the same model do. The two
shared oneuuid, so the list returned one entry short and theuuidthat
went missing was no longer accepted byselectAudioDevice. Selecting a
device triggers a re-evaluation, so the list shrank immediately after a
selection.
iOS
-
Fixed
PreflightTestrejection paths. -
Fixed
Voice.connectnever settling when CallKit rejected the start-call
transaction, for example while another call was already active. The promise
stayed pending for the lifetime of the application, so the caller saw a hang
rather than an error. It now rejects with the reason CallKit reported.The same promise is now owned by whichever path settles it first, keyed by
the call's UUID. Previously the resolver was stored only after the CallKit
transaction had already started, so a fast failure could still find no
resolver and hang; the success path did not clear it, so a later failure
could settle an already-settled promise; and it was read and written from two
queues without synchronization. -
Fixed
Voice.connectnever settling when the Twilio Voice iOS SDK declined
to create the call. It now rejects with anInvalidStateError. -
Fixed
AudioDevice.uuidchanging whenever the available audio devices were
re-evaluated, the same defect fixed on Android above. A device now keeps its
uuidfor as long as it remains available. -
Fixed the speaker output override never being cleared. After the speaker had
been selected once, selecting any other device moved only the input, so audio
kept playing out of the speaker andgetAudioDevicescorrectly reported
Speakeras the active route.