logo

Expo Local Notifications in 2026: Schedule Alerts and Handle Permissions on iOS & Android

Notifications are a key feature in mobile apps, allowing you to remind users about important events, tasks, or updates—even when the app isn't actively running. With Expo, implementing local notifications is straightforward, but there are some nuances to understand, especially around permissions and platform behavior. In this tutorial, we'll start with a simple local notification example to demonstrate the basics, then show how users can schedule notifications for a custom time. Along the way, we'll cover important considerations for both iOS and Android, including how to request and check permissions, handle denied notifications, and ensure your alerts are delivered reliably.

If you haven't set up your project yet, make sure to follow the setup and installation guide for Expo before continuing. This article was written using **Expo SDK 54.0.33 and **Expo Notifications v0.32.16.

Before you can use notifications in your Expo app, you need to install the required packages. Expo provides a built-in notifications API, but some packages need to be installed depending on your workflow.

npx expo install expo-notifications

Handling Notification Permissions

Foreground vs Background:

The “foreground” refers to when your app is currently open and visible on the user’s device. Notifications behave differently depending on whether the app is in the foreground, running in the background, or completely closed (“killed”). On iOS, notifications in the foreground require a notification handler to show alerts and sounds, while Android shows them automatically.

Before your app can send notifications, it must have permission from the user. On iOS, the system prompt appears only once per app install, so if a user denies it, they must manually enable notifications in Settings → Your App → Notifications. On Android, modern versions allow re-requesting permission, but if the user permanently denies it, the app cannot prompt again. Because of this, it's important to check the current permission status before scheduling any notifications, and provide clear guidance or a button to open settings if needed. Properly handling permissions ensures that your notifications are delivered reliably and prevents common issues like silent failures or missed alerts.

Device Compatibility Note: Local notifications generally work on iOS and Android, but behavior differs across environments. On iOS, you must handle foreground notifications using Notifications.setNotificationHandler, and denied permissions prevent notifications from appearing. Testing on real devices is essential, as simulators and Expo Go cannot fully replicate background or killed app behavior.

Simple Expo Local Notification Example


import * as Notifications from "expo-notifications";
import { useEffect } from "react";
import { Button, Text, View } from "react-native";

// Configure how notifications are handled when received
Notifications.setNotificationHandler({
  handleNotification: async () => ({
    shouldShowBanner: true,
    shouldPlaySound: true,
    shouldSetBadge: false,
  }),
});

export default function App() {
  useEffect(() => {
    requestPermissions();
  }, []);

  async function requestPermissions() {
    const { status } = await Notifications.requestPermissionsAsync();
    if (status !== "granted") {
      alert("Permission for notifications not granted!");
    }
  }

  async function scheduleNotification() {
    await Notifications.scheduleNotificationAsync({
      content: {
        title: "Hello!",
        body: "This is a local notification from Expo!",
        sound: true,
      },
      trigger: {
        type: Notifications.SchedulableTriggerInputTypes.TIME_INTERVAL,
        seconds: 5, // notification appears after 5 seconds
      },
    });
  }

  return (
    <View
      style={{
        flex: 1,
        justifyContent: "center",
        alignItems: "center",
      }}
    >
      <Text>Local Notification Test</Text>
      <Button
        title="Push the button to receive a notification in 5s"
        onPress={scheduleNotification}
      />
    </View>
  );
}

  

When the user opens the app, it requests notification permission. If the permission is granted and the user clicks the test button, the app schedules a notification to appear after 5 seconds. The notification will be delivered even if the app is in the background or closed. The core syntax for Notifications.scheduleNotificationAsync() requires a title, body, and a trigger; other fields are optional. If the user denied permission earlier, the system will not show the permission prompt again, and any notifications will not be delivered. In this case, the app will display an alert saying: "Permission for notifications not granted!". This ensures the user knows that notifications cannot be scheduled unless they enable permission in the device settings. You should keep in mind that this alert must be explicitly created in your code as shown in the example above. Otherwise, the app will not notify the user that permission is missing.


Scheduled Expo Notification Example (User Picks Time)

You need to install the @react-native-community/datetimepicker package for the following example.

npm i @react-native-community/datetimepicker

In the example below, users can choose the time when the notification should be delivered.


import DateTimePicker from "@react-native-community/datetimepicker";
import * as Notifications from "expo-notifications";
import React, { useEffect, useState } from "react";
import { Button, Platform, Text, View } from "react-native";

Notifications.setNotificationHandler({
  handleNotification: async () => ({
    shouldShowAlert: true,
    shouldPlaySound: true,
    shouldSetBadge: false,
  }),
});

export default function App() {
  const [date, setDate] = useState(new Date());
  const [showPicker, setShowPicker] = useState(false);

  // Request notification permissions on app start
  useEffect(() => {
    (async () => {
      const { status } = await Notifications.requestPermissionsAsync();
      if (status !== "granted") {
        alert("Permission for notifications not granted!");
      }
    })();
  }, []);

  // Handle DateTimePicker change
  const onChange = (event, selectedDate) => {
    setShowPicker(Platform.OS === "ios");
    if (selectedDate) 
     setDate(selectedDate);
  };
  const scheduleNotification = async () => {
    const trigger = {
      type: Notifications.SchedulableTriggerInputTypes.DATE,
      date,
    }; // trigger is a Date object

    await Notifications.scheduleNotificationAsync({
      content: {
        title: "Reminder",
        body: "This is your scheduled notification!",
      },
      trigger: trigger,
    });
    alert(`Notification scheduled for ${date.toLocaleString()}`);
  };

  return (
    <View style={{ flex: 1, justifyContent: "center", alignItems: "center" }}>
      <Text>Select time for your notification:</Text>
      <Button title="Pick Time" onPress={() => setShowPicker(true)} />
      {showPicker && (
        <DateTimePicker
          value={date}
          mode="datetime"
          is24Hour={true}
          display="default"
          onChange={onChange}
        />
      )}
      <Button title="Schedule Notification" onPress={scheduleNotification} />
    </View>
  );
}
  

On iOS, local notifications do not display alerts or play sounds when the app is open by default. To fix this, you need to set a notification handler using Notifications.setNotificationHandler at the top level of your app (e.g., in App.js) before scheduling any notifications:


    import * as Notifications from 'expo-notifications';

Notifications.setNotificationHandler({
  handleNotification: async () => ({
    shouldShowAlert: true,   // show notification as a pop-up
    shouldPlaySound: true,   // play a sound
    shouldSetBadge: false,   // do not update app icon badge
  }),
});

By calling Notifications.setNotificationHandler, you tell iOS how to display notifications when the app is running. Without it, scheduled notifications may still fire in the background, but users won't see them if the app is open, which can be confusing.


Opening Device Settings for Denied Permissions

If the user denies notification permissions on iOS, the system won't show the permission prompt again. In this case, you can guide the user to your app's settings so they can manually enable notifications.


import { useEffect } from "react";
import { Alert, Linking, Platform } from "react-native";
import * as Notifications from "expo-notifications";

const openSettings = () => {
  if (Platform.OS === "ios") {
    Linking.openURL("app-settings:");
  } else {
    Linking.openSettings();
  }
};

useEffect(() => {
  (async () => {
    const { status } = await Notifications.requestPermissionsAsync();

    if (status !== "granted") {
      Alert.alert(
        "Notifications Disabled",
        "Please enable notifications in your device settings.",
        [
          { text: "Cancel", style: "cancel" },
          { text: "Open Settings", onPress: openSettings },
        ]
      );
    }
  })();
}, []);

Conclusion

In this article, we covered how to schedule local notifications in an Expo app, including requesting permissions, handling denied permissions, and scheduling notifications for a specific day and time. We also explained the differences between iOS and Android, and how to ensure notifications appear even when the app is in the foreground. With this knowledge, you can now enhance your apps with timely alerts, and you're ready to explore more advanced topics like remote notifications or recurring reminders.

Local notifications are generated directly by the application. For notifications delivered from a backend service, see the guide on remote notifications using Expo Push Notifications and Firebase Cloud Messaging (FCM).

Check out our other Expo articles.

To learn more about Expo, check out our detailed Expo tutorial.