Notifications are a simple and common way to engage users of an app. By default, when a user taps a notification, the app usually opens to its main screen.
In some cases, you may want the notification to take the user directly to a specific page where they can perform a particular action. This is where push deep linking becomes useful.
What is Deep Link & How To work
In mobile development, a deep link is a link that opens an app and takes the user directly to a specific screen. When the user taps the link, the operating system checks which app is associated with it and opens that app.
To make this work, developers define a URL scheme that helps the mobile platform recognize which app should handle the link.
With Expo, you can define a custom URL scheme for your app in the app configuration file. This allows your app to be opened through deep links.
Under app.json file, define app custom scheme
{
"expo": {
"scheme": "myapp"
}
}Sending Push Notifications
Within Expo/React Native ecosystem, there's 2 common way of push Notifications:
Expo Push Notification Service
Expo provides a managed notification service built on top of Firebase Cloud Messaging (FCM) and Apple Push Notification service (APNs), which is integrated through expo-notifications.
It offers a simple way to listen for and handle both local and remote notifications without relying on third-party packages.
Firebase Cloud Messaging
Firebase Cloud Messaging (FCM) is a service provided by Google through Firebase. It offers a unified way to send remote push notifications to both Android and iOS devices.
Google does not provide an official Firebase SDK specifically for React Native or Expo. However, the community maintains Firebase integrations for the React Native ecosystem, such as React Native Firebase.
We have discussed in detail how to send push notifications using both services in this article
Defining Deep Linking Through Notification Payload
You can enable deep linking by including a target screen or URL in the notification payload. When the user taps the notification, the app reads this data and navigates to the specified screen.
This approach allows you to control where users land in the app directly from the notification, making it easier for them to take the intended action.
Deep Linking Through Expo Push Service
When using Expo push notification service, you need to include the target link URL inside the data field of the notification payload.
fetch("https://exp.host/--/api/v2/push/send", {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
to: "ExponentPushToken[xxxxxxxxxxxxxxxxxxxxxx]",
title: "Save 25% OFF Discount!",
body: "Save up to 25% for every purchase until Cyber Monday.",
data: {
url: "/products?discountCode=CYBERMONDAY25",
},
}),
});When sending notifications to a large audience, Expo recommends using server-side SDKs to handle the heavy work for you.
You can learn more here: https://docs.expo.dev/push-notifications/sending-notifications/#send-push-notifications-using-a-server
Deep Linking Through Firebase Cloud Messaging
Firebase provides Firebase Admin SDK, which is a unified SDK used to interact with different Firebase services. It is available in multiple programming languages and is mainly used on the server side.
Below is an example using the Node.js runtime. You can also find implementations for other programming languages here
// This registration token comes from the client FCM SDKs.
const registrationToken = "YOUR_REGISTRATION_TOKEN";
const message = {
notification: {
title: "Save 25% OFF Discount!",
body: "Save up to 25% for every purchase until Cyber Monday.",
},
data: {
url: "/products?discountCode=CYBERMONDAY25",
},
token: registrationToken,
};
// Send a message to the device corresponding to the provided
// registration token.
getMessaging()
.send(message)
.then((response) => {
// Response is a message ID string.
console.log("Successfully sent message:", response);
})
.catch((error) => {
console.log("Error sending message:", error);
});Listening to App opened Through Notification
When a device receives a notification while the app is closed or running in the background, the expected behavior is that the user taps the notification and the app opens directly to the intended screen.
Both Expo and Firebase mobile SDKs provide mechanisms to detect when an app is opened from a notification and to access the notification payload.
Below are code snippets showing how this is implemented in each SDK:
Via Expo Push Service
As we stated early, Expo provides expo-notifications package to register device for push notification and listen to notification related events
import { useEffect } from "react";
import * as Notifications from "expo-notifications";
import * as Linking from "expo-linking";
function useNotificationObserver() {
useEffect(() => {
function redirect(notification) {
const url = notification.request.content.data?.url;
if (typeof url === "string") {
// Open target Link
const linkingUrl = `myapp://${url}`;
Linking.openURL(linkingUrl);
}
}
const response = Notifications.getLastNotificationResponse();
if (response?.notification) {
redirect(response.notification);
}
const subscription = Notifications.addNotificationResponseReceivedListener(
(response) => {
redirect(response.notification);
},
);
return () => {
subscription.remove();
};
}, []);
}Via Firebase Cloud Messaging
The community-maintained Firebase Cloud Messaging SDK for React Native (React Native Firebase) provides event listeners that let you detect when the app is opened from a notification.
import { useEffect } from "react";
import messaging from "@react-native-firebase/messaging";
import * as Linking from "expo-linking";
function useNotificationObserver() {
useEffect(() => {
function redirect(remoteMessage) {
const url = remoteMessage.data?.url;
if (typeof url === "string") {
// Open target Link
const linkingUrl = `myapp://${url}`;
Linking.openURL(linkingUrl);
}
}
/*
* When the app is completely closed (quit state) and opened via a notification tap,
* onNotificationOpenedApp will not trigger.onNotificationOpenedApp will not trigger.
* Instead, use getInitialNotification once during the app's initialization
*/
messaging()
.getInitialNotification()
.then((remoteMessage) => {
if (remoteMessage) {
redirect(remoteMessage);
}
});
// Listener for background-to-foreground interaction
const unsubscribe = messaging().onNotificationOpenedApp((remoteMessage) => {
redirect(remoteMessage);
});
return unsubscribe;
}, []);
}Handling Linking with Expo Router & React Navigation
Depending on the routing library your app uses—Expo Router or React Navigation—you need to handle notification navigation differently, especially when mapping the url field from your notification payload to an in-app screen.
With Expo Router, navigation is simpler because it uses a file-based routing system.
Deep linking is essentially built in. You just need to ensure that the url included in your notification data matches your app’s file structure (relative to the app/ directory).
For example, if your notification contains url: /product/123 it should correspond directly to a file like app/product/[id].tsx.
When the app opens from the notification, Expo Router can automatically resolve and navigate to that route.
With React Navigation, you need to explicitly define how URLs map to your app screens using a linking configuration object.
This involves setting up a linking config where you declare screen names and their corresponding paths.
For example, you map a screen like ProductDetails to a path such as /product/:id.
React Navigation then uses this configuration to parse the incoming deep link from the notification payload and navigate to the correct screen.
You can refer to the official guide on configuring deep linking for more details: https://reactnavigation.org/docs/deep-linking/?framework=expo&config=dynamic#configuring-react-navigation.
In both cases, the key idea is the same: ensure the url field in your notification payload matches a valid route in your navigation system so the app can correctly resolve and navigate when opened from a push notification.
