npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

react-native-alarm-scheduler

v0.1.7

Published

Native alarm scheduling for React Native and Expo apps

Readme

react-native-alarm-scheduler

Native alarm scheduling for React Native and Expo apps with Android exact alarms and iOS AlarmKit support.

react-native-alarm-scheduler is an Expo Modules API package for apps that need user-visible alarms, not just delayed background work. It exposes a small TypeScript API for checking alarm permissions, scheduling app-owned alarms, listing/canceling scheduled alarms, and opening native alarm surfaces where the operating system allows it.

Features

  • Android alarm scheduling through AlarmManager.setAlarmClock.
  • Android system Clock integration through AlarmClock.ACTION_SET_ALARM and AlarmClock.ACTION_SHOW_ALARMS.
  • iOS native alarm scheduling through AlarmKit on iOS 26+.
  • iOS AlarmKit metadata and presentation options for app-resolved alarm routing.
  • iOS AlarmKit App Intent actions for native stop and secondary alarm buttons.
  • iOS AlarmKit default or named custom alert sounds.
  • iOS AlarmKit backup timer helpers for completion-gated alarm flows.
  • Config plugin for Android permissions and NSAlarmKitUsageDescription.
  • Typed TypeScript API for React Native and Expo apps.
  • Explicit unsupported behavior for platforms or OS versions that cannot schedule native alarms.

Platform support

| Capability | Android OS support | iOS OS support | Available in this package | | --- | --- | --- | --- | | Check alarm authorization/capability | Yes. Uses exact alarm capability checks where required. | Yes on iOS 26+ through AlarmKit authorization state. Older iOS reports unavailable. | getPermissionsAsync() | | Request alarm authorization | Yes. Opens exact alarm settings on Android 12+ when needed. | Yes on iOS 26+ through AlarmKit. | requestPermissionsAsync() | | Open alarm/app settings | Yes. Opens exact alarm or app settings. | Yes. Opens app settings. | openAlarmSettingsAsync() | | Schedule an app-owned alarm | Yes. Uses AlarmManager.setAlarmClock for user-visible alarms. | Yes on iOS 26+ through AlarmKit. | scheduleAlarmAsync() | | Set native alarm sound | Default alarm sound. | Default or named custom AlarmKit sound. | ios.soundName | | Cancel an app-owned alarm | Yes. Cancels alarms created by this package. | Yes on iOS 26+ for alarms created by this package. | cancelAlarmAsync(id) | | List app-owned alarms | Stored by this package. Android does not expose all system Clock alarms to apps. | Stored by this package. iOS does not expose all Clock app alarms to apps. | getScheduledAlarmsAsync() | | Read current alarm context | No. Android alarm launches use the app launcher intent. | Yes. Uses package-stored metadata plus AlarmKit alarm state when available. | getCurrentAlarmContextAsync() | | Read native alarm actions | No. | Yes. Records built-in AlarmKit App Intent actions for native stop and secondary buttons. | getPendingAlarmActionsAsync() | | Schedule/cancel a native backup alarm | No-op. | Yes on iOS 26+ through a deterministic AlarmKit backup timer id. | scheduleNativeAlarmBackupAsync(), cancelNativeAlarmBackupAsync() | | Create an alarm in the system Clock app | Yes. Uses AlarmClock.ACTION_SET_ALARM. | No public iOS API exists for creating Clock app alarms. | setSystemAlarmAsync() on Android only | | Open the system alarm app | Yes. Uses AlarmClock.ACTION_SHOW_ALARMS. | Best effort only through a Clock URL; iOS may ignore it. | openSystemAlarmAppAsync() | | Fire JS event when an alarm triggers | Limited by app process state. | Limited by app process state. | onAlarmTriggered is declared; Android also shows a native notification. | | Web support | Not applicable. | Not applicable. | No scheduling support; methods return unavailable or throw explicit unsupported errors. |

Requirements

  • Expo app with native prebuild support.
  • Android API 24+.
  • iOS 15.1+ for package compatibility.
  • iOS 26 SDK and iOS 26 runtime for actual AlarmKit scheduling.

This module uses native code, so it is not available inside Expo Go. Use a development build or a prebuilt native app.

For bare React Native apps, install and configure Expo Modules first, then install this package. Expo apps already include the required runtime.

Install

npm install react-native-alarm-scheduler

Configure

Add the config plugin to your app config before running expo prebuild:

{
  "expo": {
    "plugins": [
      [
        "react-native-alarm-scheduler",
        {
          "alarmKitUsageDescription": "Allow this app to schedule alarms.",
          "addExactAlarmPermission": true,
          "addNotificationPermission": true,
          "iosAlarmSounds": ["./assets/audio/bollywood-alarm.mp3"]
        }
      ]
    ]
  }
}

Plugin options:

| Option | Type | Default | Description | | --- | --- | --- | --- | | alarmKitUsageDescription | string | Allow this app to schedule alarms that can alert you at the selected time. | Adds NSAlarmKitUsageDescription for iOS AlarmKit authorization. | | addExactAlarmPermission | boolean | true | Adds android.permission.SCHEDULE_EXACT_ALARM. | | addNotificationPermission | boolean | true | Adds android.permission.POST_NOTIFICATIONS. | | iosAlarmSounds | string[] | [] | Adds custom sound files to the iOS app bundle Resources build phase so AlarmKit can resolve them by filename. |

The plugin also enables NSSupportsLiveActivities for iOS AlarmKit intents and adds com.android.alarm.permission.SET_ALARM for Android system Clock alarm intents.

Then rebuild native projects:

npx expo prebuild
npx expo run:android
npx expo run:ios

Usage

Schedule an app-owned alarm:

import ExpoAlarm from 'react-native-alarm-scheduler';

const permissions = await ExpoAlarm.requestPermissionsAsync();

if (permissions.canScheduleExactAlarms) {
  const alarm = await ExpoAlarm.scheduleAlarmAsync({
    hour: 7,
    minute: 30,
    title: 'Morning alarm',
    weekdays: [1, 2, 3, 4, 5],
    ios: {
      metadata: {
        route: 'alarm-detail',
      },
      alertTitle: 'Morning alarm',
      alertActionMode: 'openMissionOnly',
      secondaryButtonTitle: 'Open',
      stopIntentBehavior: 'rescheduleImmediate',
      secondaryButtonBehavior: 'openApp',
    },
  });

  await ExpoAlarm.cancelAlarmAsync(alarm.id);
}

Weekdays use ISO numbering: 1=Monday through 7=Sunday.

Open Android's system alarm UI:

await ExpoAlarm.openSystemAlarmAppAsync();

Create a system Clock alarm on Android:

await ExpoAlarm.setSystemAlarmAsync({
  hour: 8,
  minute: 0,
  title: 'Leave for work',
  weekdays: [1, 2, 3, 4, 5],
  showUi: true,
});

List and cancel app-owned alarms:

const alarms = await ExpoAlarm.getScheduledAlarmsAsync();

for (const alarm of alarms) {
  await ExpoAlarm.cancelAlarmAsync(alarm.id);
}

API

getPermissionsAsync()

Returns the current alarm capability state:

type AlarmPermissionResponse = {
  platform: 'android' | 'ios';
  status: 'authorized' | 'denied' | 'notDetermined' | 'unavailable' | 'unknown';
  canScheduleExactAlarms: boolean;
  canOpenSettings: boolean;
};

On Android, canScheduleExactAlarms reflects whether exact alarm scheduling is currently allowed. On iOS, it is true only when AlarmKit is available and authorized.

requestPermissionsAsync()

Requests or opens the native permission surface where possible, then returns AlarmPermissionResponse.

On Android 12+, this opens the exact alarm settings screen if exact alarms are not currently allowed. On iOS 26+, this requests AlarmKit authorization.

openAlarmSettingsAsync()

Opens the relevant alarm or app settings screen and returns whether the open action was started.

scheduleAlarmAsync(alarm)

Schedules an app-owned native alarm and returns the stored alarm.

type AlarmScheduleInput = {
  id?: string;
  hour: number;
  minute: number;
  title?: string;
  weekdays?: AlarmWeekday[];
  timestamp?: number;
  showUi?: boolean;
  ios?: {
    metadata?: Record<string, string | number | boolean>;
    alertTitle?: string;
    alertActionMode?: 'default' | 'openMissionOnly';
    stopButtonTitle?: string;
    secondaryButtonTitle?: string;
    countdownTitle?: string;
    stopIntentBehavior?: 'recordOnly' | 'openApp' | 'rescheduleImmediate';
    secondaryButtonBehavior?: 'openApp' | 'recordOnly' | 'none';
    soundName?: string;
  };
};

type AlarmWeekday = 1 | 2 | 3 | 4 | 5 | 6 | 7;

type ScheduledAlarm = {
  id: string;
  hour: number;
  minute: number;
  title: string;
  weekdays: AlarmWeekday[];
  timestamp: number;
  platform: 'android' | 'ios';
  metadata?: Record<string, string | number | boolean>;
};

Notes:

  • hour uses 24-hour time from 0 to 23.
  • minute must be from 0 to 59.
  • timestamp is milliseconds since Unix epoch. If omitted, the module schedules the next matching hour and minute.
  • Android accepts any string id.
  • iOS AlarmKit requires id to be a UUID string when you provide one.
  • ios.metadata is stored by the package and included in AlarmKit metadata. The package always adds alarmId and title.
  • iOS presentation options customize AlarmKit text only. They are not Android-style launch intents and do not force a React Native route.
  • ios.soundName maps to AlarmKit's AlertConfiguration.AlertSound.named(soundName). Omit it to use the system default sound. The sound name should be the exact bundled filename, including the extension, for example bollywood-alarm.mp3. In Expo apps, add the file path to the config plugin's iosAlarmSounds array so prebuild adds it to the iOS app bundle.
  • ios.alertActionMode: 'openMissionOnly' prefers AlarmKit's newer secondary-only alert presentation when the runtime supports it. This omits the package-configured stop button and makes the secondary button the visible app action.
  • ios.stopIntentBehavior: 'recordOnly' installs a built-in AlarmKit App Intent that records a nativeStop action when the system stop control is pressed.
  • ios.stopIntentBehavior: 'openApp' records nativeStop and asks iOS to foreground the app immediately. The action record includes foregroundRequested: true; iOS does not provide a reliable success callback to the package.
  • ios.stopIntentBehavior: 'rescheduleImmediate' records nativeStop, asks iOS to foreground the app, and attempts to schedule a short backup AlarmKit timer until JS calls completeNativeAlarmAsync(alarmId) or clearBypassAsync(alarmId). Backup alarms use a deterministic native UUID derived from the original logical alarmId, so each re-arm cancels/replaces the previous backup instead of accumulating retry alarms.
  • ios.secondaryButtonBehavior: 'openApp' installs a built-in AlarmKit App Intent that records secondaryOpen and asks iOS to open the app. Use recordOnly to record without foregrounding, or none to omit the secondary intent.
  • AlarmKit may still expose system-owned close/stop affordances that do not invoke package App Intents. Use getNativeAlarmDebugStateAsync(alarmId) to inspect which alert initializer and buttons were used, and treat strict completion enforcement as limited by public AlarmKit APIs.

cancelAlarmAsync(id)

Cancels an app-owned alarm by id. Returns true when a native or stored alarm was removed.

getScheduledAlarmsAsync()

Returns the app-owned alarms stored by this module.

getCurrentAlarmContextAsync()

Returns iOS alarm context for app launch or resume routing:

type AlarmContext = {
  id: string;
  metadata?: Record<string, string | number | boolean>;
  state?: 'scheduled' | 'alerting' | 'countdown' | 'paused';
  nativeAlarmId?: string;
};

On iOS 26+, this reads AlarmKit alarms owned by the app and joins them with metadata stored by this package. If the active native alarm is the deterministic backup timer, id remains the original logical alarm id and nativeAlarmId contains the backup UUID. If a one-shot alarm recently fired and AlarmKit already removed it from the daemon store, the package can still return the stored metadata for a short recovery window. On Android and Web this returns null.

getPendingAlarmActionsAsync()

Returns native AlarmKit action records that happened while JS may not have been running:

type AlarmAction = {
  id: string;
  alarmId: string;
  action: 'nativeStop' | 'secondaryOpen' | 'snooze' | 'dismiss';
  timestamp: number;
  foregroundRequested?: boolean;
  rescheduled?: boolean;
  rescheduledAlarmId?: string;
  retryScheduledFor?: number;
  backupAlarmId?: string;
  backupScheduledFor?: number;
  backupDelaySeconds?: number;
};

nativeStop means the user pressed the system alarm stop control. Treat it as a bypass signal, not as successful completion.

getPendingNativeAlarmHandoffAsync()

iOS only. Returns the latest native AlarmKit intent handoff recorded by the package, or null if none exists. This is a single durable handoff slot written directly by the native App Intent before JS listeners run, and is useful for app-launch routing:

const handoff = await ExpoAlarm.getPendingNativeAlarmHandoffAsync();

if (handoff?.action === 'nativeStop' || handoff?.action === 'secondaryOpen') {
  await ExpoAlarm.scheduleNativeAlarmBackupAsync(handoff.alarmId, 0.1);
  // route to your alarm handling UI
}

Use clearPendingNativeAlarmHandoffAsync() after your app has consumed the handoff.

clearPendingNativeAlarmHandoffAsync()

Clears the durable native handoff slot. This does not clear the full action history returned by getPendingAlarmActionsAsync().

clearPendingAlarmActionsAsync(ids?)

Clears pending native action records. Pass action record id values to clear specific records, or omit ids to clear all records.

completeNativeAlarmAsync(alarmId)

Marks the native alarm flow complete for rescheduleImmediate. Call this only after the user satisfies your app's completion condition. This stops future native stop intents from scheduling backup alarms for that alarmId, cancels the original native alarm when active, cancels the deterministic backup alarm and any legacy tracked retry alarms for that logical alarm id, and clears pending native action records for that alarm.

scheduleNativeAlarmBackupAsync(alarmId, delaySeconds?)

iOS 26+ only. Schedules a short AlarmKit backup timer for an existing logical alarm id and returns:

type NativeAlarmBackupResult = {
  alarmId: string;
  backupAlarmId: string;
  scheduled: boolean;
  scheduledFor?: number;
  delaySeconds: number;
};

The backup id is deterministic for the primary alarm id. Calling this repeatedly cancels/replaces the same backup timer. This is useful when your app processes a native alarm handoff from getPendingAlarmActionsAsync() or finds an alerting alarm from getCurrentAlarmContextAsync() and needs to re-arm before presenting app UI.

cancelNativeAlarmBackupAsync(alarmId)

iOS 26+ only. Cancels the deterministic backup timer for a logical alarm id and removes any legacy retry ids tracked by older package versions.

clearBypassAsync(alarmId)

Clears the completion marker for an alarm id, allowing rescheduleImmediate retries again for that alarm id. Prefer resetNativeAlarmCompletionAsync(alarmId) for clearer naming.

resetNativeAlarmCompletionAsync(alarmId)

Alias for clearBypassAsync(alarmId) with clearer semantics.

getNativeAlarmDebugStateAsync(alarmId)

Returns native retry/debug state:

type NativeAlarmDebugState = {
  alarmId: string;
  isComplete: boolean;
  activeRetryAlarmIds: string[];
  pendingActions: AlarmAction[];
  pendingHandoff?: AlarmAction | null;
  intentDebugCounts?: Record<string, number>;
  currentContext: AlarmContext | null;
  alertActionMode?: 'default' | 'openMissionOnly';
  stopButtonIncluded?: boolean;
  secondaryButtonIncluded?: boolean;
  secondaryButtonBehavior?: 'openApp' | 'recordOnly' | 'none';
  stopIntentBehavior?: 'recordOnly' | 'openApp' | 'rescheduleImmediate';
  alertInitializer?: 'secondaryOnly' | 'legacyStopButton';
  runtimeSupportsSecondaryOnlyAlert?: boolean;
  sound?: 'default' | 'named';
  soundName?: string;
};

If alertActionMode is openMissionOnly but alertInitializer is legacyStopButton, the runtime required the legacy stop-button presentation and the package cannot remove that AlarmKit stop affordance.

setSystemAlarmAsync(alarm)

Android only. Sends an AlarmClock.ACTION_SET_ALARM intent to create an alarm in the user's Clock app. Returns false if no compatible Clock activity is available.

iOS does not expose a public API for creating alarms in the system Clock app, so this method throws on iOS.

openSystemAlarmAppAsync()

Opens the Android system Clock alarm screen. On iOS this uses a best-effort Clock URL and may return false or do nothing depending on the OS.

Events

const subscription = ExpoAlarm.addListener('onAlarmTriggered', (alarm) => {
  console.log(alarm);
});

const actionSubscription = ExpoAlarm.addListener('onAlarmAction', (action) => {
  console.log(action);
});

const stateSubscription = ExpoAlarm.addListener('onAlarmStateChange', (event) => {
  console.log(event);
});

subscription.remove();
actionSubscription.remove();
stateSubscription.remove();

Currently Android shows a native notification when an app-owned alarm fires. The event is declared for API stability; delivery to a running JS runtime depends on app process state.

Android behavior

Android exact alarm behavior depends on OS version, target SDK, user settings, and Play policy. The module checks canScheduleExactAlarms() before reporting alarm capability and uses setAlarmClock() for user-visible alarm semantics.

If exact alarms are denied, call requestPermissionsAsync() or openAlarmSettingsAsync() and ask the user to enable Alarms & reminders for your app.

iOS behavior

iOS alarm scheduling uses AlarmKit. The app must:

  • Build with an SDK that includes AlarmKit.
  • Run on iOS 26 or newer.
  • Include a non-empty NSAlarmKitUsageDescription.
  • Receive user authorization through requestPermissionsAsync().

Older iOS versions return status: 'unavailable'.

AlarmKit does not expose Android-style Intent or PendingIntent launch routing. For route-specific behavior, put route context such as alarmId or a screen name in ios.metadata, then call getCurrentAlarmContextAsync() and getPendingAlarmActionsAsync() on app launch or resume and navigate from JavaScript. Foreground listeners such as onAlarmAction and onAlarmStateChange are best effort; apps should reconcile from the async getters after launch.

Development

npm install
npm run build
npm run lint
npm run prepublishOnly

Run the example app:

cd example
npm install
npm run android
npm run ios