Custom alarm sounds
Let users choose a different local audio file for each alarm.
Silent alarms
Set silent: true in either platform’s options when the alarm should present normally without
playing audio:
await AlarmScheduler.scheduleAlarmAsync({
hour: 7,
minute: 0,
ios: { silent: true },
android: { silent: true, vibrate: true },
});silent takes precedence over soundUri and soundName. On Android it skips audio playback and
alarm-volume enforcement; vibrate remains independent, so silent: true, vibrate: true is a
vibrate-only alarm. The notification and full-screen ringing UI still appear.
On a physical iOS 26+ device, the package uses its own bundled silent CAF while AlarmKit continues to own the alert presentation and vibration. The config plugin adds this asset automatically. If the asset is missing, scheduling fails instead of falling back to an audible system sound.
Silent AlarmKit alarms require a physical device
The iOS Simulator cannot safely use named AlarmKit sounds, including the package’s silent sound. Silent scheduling is therefore rejected in the Simulator rather than becoming unexpectedly audible.
User-selected audio
Use any document or media picker that returns a readable local URI. With Expo, install the SDK-matched document picker:
npx expo install expo-document-pickerThen pass the selected URI directly to the alarm:
import * as DocumentPicker from 'expo-document-picker';
import AlarmScheduler from 'react-native-alarm-scheduler';
const result = await DocumentPicker.getDocumentAsync({
type: 'audio/*',
copyToCacheDirectory: true,
});
if (!result.canceled) {
await AlarmScheduler.scheduleAlarmAsync({
hour: 7,
minute: 0,
soundUri: result.assets[0].uri,
});
}soundUri is common to both platforms. You may instead set ios.soundUri or android.soundUri
when the platforms should use different files. Each alarm can use a different URI.
The scheduler imports the audio while scheduleAlarmAsync runs, so the alarm does not depend on a
temporary picker permission or cache file later:
- iOS: converts the first 29 seconds to a PCM CAF file in the app’s
Library/Soundsdirectory, which AlarmKit can resolve while the app is not running. - Android: copies the original into the app’s private files directory and loops it through the native alarm audio stream.
The imported copy is replaced when the same alarm id is rescheduled and removed when the alarm is cancelled. The source file may be moved or deleted after scheduling.
Readable files only
DRM-protected Apple Music, Spotify and similar subscription tracks are not readable local audio files and cannot be imported. Exported or downloaded audio files from Files, Downloads, cloud-drive providers and media-creation apps work when the picker grants access.
Why iOS uses the first 29 seconds
AlarmKit uses the system alert-sound facility. Apple requires alert sounds to be under 30 seconds
and stored in the app bundle or Library/Sounds. The module performs the conversion and trim so a
normal user-selected MP3, M4A, WAV, AIFF or CAF file can be used without a build-time config change.
See Apple’s AlarmKit custom-sound guidance.
iOS Simulator uses the system default
iOS 26.x Simulator can crash SpringBoard inside Apple’s private ToneLibrary when AlarmKit starts an
external sound. To keep development builds stable, the package uses the system default sound in the
Simulator and reports soundFallbackReason: 'iosSimulatorCustomSoundUnsupported' in native debug
state. Test custom sound playback on a physical iOS 26+ device. Android emulators are unaffected.
Android fallback behavior
The foreground ring service plays the selected file. If Android refuses to start that service and the package must fall back to a system notification channel, it uses the system alarm tone because the notification service cannot read the app’s private imported file.
Build-time bundled sounds
Bundled sounds remain useful when the developer owns the fixed sound catalog. On iOS, add compatible sound files to the config plugin:
{
"expo": {
"plugins": [
[
"react-native-alarm-scheduler",
{ "iosAlarmSounds": ["./assets/audio/morning-alarm.caf"] }
]
]
}
}Reference the exact filename:
await AlarmScheduler.scheduleAlarmAsync({
hour: 7,
minute: 0,
ios: { soundName: 'morning-alarm.caf' },
});On Android, place the file in android/app/src/main/res/raw and use its resource name:
await AlarmScheduler.scheduleAlarmAsync({
hour: 7,
minute: 0,
android: { soundName: 'morning_alarm' },
});For audible alarms, runtime soundUri takes precedence over bundled soundName; silent takes
precedence over both.
Staying audible on Android
Playback loops on STREAM_ALARM, which ignores the ringer and Do Not Disturb. On top of that, the
package defends against the volume-down escape hatch:
await AlarmScheduler.scheduleAlarmAsync({
hour: 7,
minute: 0,
android: {
enforceVolume: true,
restoreVolume: true,
volume: 1,
vibrate: true,
},
});With enforceVolume on, the alarm stream is pinned at volume for the duration of the ring and
re-raised whenever anything lowers it. The user’s original level is restored afterwards unless
restoreVolume is false.
iOS alarm volume is owned by AlarmKit and is not configurable from the package.