Blog

Tutorials

Push notifications in react native: A Practical Guide to Engagement

Learn how to implement push notifications in react native with Expo, from permissions to backend logic and advanced tips to boost user engagement.

Writer

Nafis Amiri

Co-Founder of CatDoes

Feb 9, 2026

Title slide with a minimalist grid background displaying the text ‘Push notifications in React Native: A Practical Guide to Engagement.

Push notifications in react native: A Practical Guide to Engagement

Implementing push notifications in React Native is the single most direct way to pull users back into your app. It’s how you turn a static piece of software into a live, dynamic channel for communication. The process boils down to a few key parts: setting up your Expo project correctly, asking for permissions on both iOS and Android, and then connecting everything to a backend that can send timely, relevant messages right to a user's device.

Why Push Notifications Are Critical For App Engagement

Before we touch a single line of code, it’s worth understanding just how powerful push notifications are. They’re so much more than just alerts; they are the lifeline between your app and your users, capable of turning casual browsers into your most active customers. Done right, they keep you top-of-mind and deliver immediate value.

Think about it this way: without notifications, your app is silent. It has to wait, hoping a user remembers it exists and decides to open it. With notifications, you get to start the conversation, reminding them about an abandoned cart, a new message, or an update they’ll actually care about.

The Real Impact on User Retention

The numbers don't lie. There's a direct link between smart push notifications and user retention. Apps that send targeted, weekly notifications can see their retention rates double or even triple compared to those that stay quiet. It’s a powerful tool for building habits and keeping people invested in what you've built.

To see how these messages fit into a bigger picture, exploring broader Push Marketing Examples can give you some great strategic context. It really shows how you can use them for proactive engagement.

A user who enables push notifications has already signaled a higher level of interest. They are far more likely to become a long-term, valuable user. Your goal isn't just to send messages; it's to earn and keep that trust.

Bridging the iOS and Android Divide with Expo

One of the classic headaches in mobile development has always been the difference between how iOS and Android handle notifications. Each platform has its own dedicated service, Apple Push Notification service (APNs) for iOS and Firebase Cloud Messaging (FCM) for Android, with completely different setup flows and permission models.

  • iOS: Apple makes you get explicit opt-in from the user before you can send anything. This user-first approach means your request for permission has to be compelling and timed perfectly.

  • Android: While traditionally more lenient, newer Android versions are moving closer to the iOS model, also requiring you to ask for permission upfront.

This is where building with React Native and Expo becomes a massive advantage. Expo’s notification libraries abstract away most of this platform-specific mess. Instead of wrestling with separate APNs and FCM integrations, you get to work with a single, unified API. For more on creating a seamless mobile experience, check out our guide on best practices for a great https://catdoes.com/blog/app-user-experience. This approach not only simplifies your code but also drastically cuts down on development time and ensures you have a consistent setup across both operating systems.

Getting Your Expo Project Ready For Notifications

Alright, with the planning out of the way, it's time to get our hands dirty and set up the project. A solid foundation is everything when it comes to implementing reliable push notifications in React Native. The good news is that Expo makes this process dramatically simpler than the old bare workflow.

Our first move is to install the main tool for the job: the expo-notifications library. This package is your one-stop shop for handling permissions, receiving messages, and managing notification behavior across both iOS and Android.

Pop open your terminal and run this command in your project’s root directory:

npx expo install expo-notifications

It's super important to use expo install here instead of npm or yarn. This little detail ensures you grab the exact version of the library that’s compatible with your current Expo SDK, which helps you dodge a whole class of frustrating bugs right from the get-go.

Configuring Your App For Android

On the Android side of things, notifications are handled by Google's Firebase Cloud Messaging (FCM). To make this work, your Expo app needs to know how to talk to your Firebase project.

This connection is made by adding a specific configuration block to your app.json or app.config.js file.

First, you'll need a Firebase project. If you don't have one, create one and add an Android app to it. As you go through the setup, Firebase will give you a google-services.json file. Instead of just dropping this file into your project, you'll reference its path inside your app.json.

Here's what that configuration should look like in your app.json:

{

"expo": {

"android": {

"googleServicesFile": "./google-services.json",

"useNextNotificationsApi": true,

"permissions": ["RECEIVE_BOOT_COMPLETED"]

}

}

}

The useNextNotificationsApi flag is a must. It opts you into the latest and greatest notification infrastructure. I also recommend adding the RECEIVE_BOOT_COMPLETED permission. It allows your app to handle scheduled notifications even after a user restarts their phone.

If you're just starting from scratch, our comprehensive React Native Expo tutorial can walk you through the entire initial setup process.

Setting Up Your Project For iOS

iOS uses the Apple Push Notification service (APNs), and its setup is a bit different. You don't need an external config file like with Android. Instead, you'll handle most of the configuration directly in app.json.

The critical piece for iOS is enabling push notifications and telling the system how your app should handle them.

Just add this block under the ios key in your app.json:

{

"expo": {

"ios": {

"entitlements": {

"aps-environment": "development"

}

}

}

}

That aps-environment key is incredibly important. It tells Apple whether your app build is for development and testing or for a production release on the App Store.

Heads up: You absolutely must set "aps-environment" to "production" before submitting your app to the App Store. If you forget, your production notifications will just fail silently, and you'll be left scratching your head.

A Much Simpler Path with Automation

Let's be honest. Manually tweaking these config files, managing Firebase projects, and dealing with APNs credentials can be a real pain. It's tedious work and a common source of errors. This is where modern development platforms can be a lifesaver.

For example, a platform like CatDoes can automate this entire setup process for you. By just describing what your app needs, its AI agents can:

  • Generate the correct app.json configuration for both iOS and Android automatically.

  • Integrate with backend services for push notifications without any manual setup.

  • Make sure all the necessary libraries and permissions are included right from the start.

This kind of automation lets you skip the boring boilerplate and focus on what really matters: crafting a great notification experience for your users.

Now that our project is configured, the next logical step is to actually ask the user for their permission to send them notifications.

Getting The User's Permission and Push Token

With the project configured, it's time to deal with the most important part of this whole setup: the user. Your app's ability to send push notifications in React Native lives or dies based on one simple thing: user consent. If they say no, it's game over.

This makes how you ask for permission absolutely critical.

Blasting a user with a system pop-up the second they open your app is a guaranteed way to get rejected. A much better approach is to provide some context first. You could show a friendly, custom screen that quickly explains why you need to send them notifications before you trigger the official iOS or Android prompt.

How to Craft the Permission Request

Thankfully, the expo-notifications library makes asking for permission pretty straightforward across both platforms. The function at the heart of it all is requestPermissionsAsync, which shows the native dialog and tells you what the user decided.

Here’s a practical, reusable function you can drop into your app. It’s smart enough to check if you already have permission before asking again.

import * as Notifications from 'expo-notifications'; import { Platform } from 'react-native';

export async function registerForPushNotificationsAsync() { let token;

if (Platform.OS === 'android') {

await Notifications.setNotificationChannelAsync('default', {

name: 'default',

importance: Notifications.AndroidImportance.MAX,

vibrationPattern: [0, 250, 250, 250],

lightColor: '#FF231F7C',

});

}

const { status: existingStatus } = await Notifications.getPermissionsAsync(); let finalStatus = existingStatus;

if (existingStatus !== 'granted') {

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

finalStatus = status;

}

if (finalStatus !== 'granted') {

alert('Failed to get push token for push notification!');

return;

}


// Learn more about projects and EAS Build at https://docs.expo.dev/

token = (await Notifications.getExpoPushTokenAsync({

projectId: 'YOUR_PROJECT_ID_HERE',

})).data;


console.log(token);

return token;

}


Heads Up: This code will only work on a real, physical device. Simulators can't generate a valid push token, which trips up a lot of developers when they're first starting out with notifications.

See that existingStatus check in the code? That little bit of logic is what stops your app from pestering users who have already said yes. It’s a small detail that makes a big difference in user experience.

Getting the Expo Push Token

Once the user hits "Allow," you get to the good part. The call to getExpoPushTokenAsync grabs the unique identifier for that specific installation of your app on that user's device. This token is your golden ticket. It's the exact address you'll use to send a notification to them.

An Expo Push Token is just a long, unique string that looks something like this: ExponentPushToken[xxxxxxxxxxxxxxxxxxxxxx].

It's generated by Expo's servers and cleverly acts as a universal address that works for both Apple (APNs) and Google (FCM). You don't have to worry about managing separate tokens for iOS and Android; Expo takes care of that translation behind the scenes.

Just remember to swap out 'YOUR_PROJECT_ID_HERE' with your actual EAS project ID. If you're not sure what it is, you can find it in your app.json file or just run npx eas project:id in your terminal.

You Have the Token. Now, Store It.

Getting the token is only half the job. A token that just sits on the user's phone is useless to your server. You absolutely must send this token to your backend and store it, usually in a database table tied to a user account.

This isn't optional for any real-world app. Here's what you need to handle:

  • User Association: Make sure the push token is linked directly to a user's ID in your database.

  • Token Management: Push tokens can change or expire. Your backend needs a way to update them when a user opens the app and remove old, invalid ones.

  • Security: Treat these tokens like you would any other piece of sensitive user data.

When you're ready to send a notification, your backend will simply look up the user, grab their stored push token, and fire off a request to the Expo Push API. We’ll get into exactly how that backend process works next.

Sending Notifications From Your Backend

Alright, so you’ve managed to get that precious Expo Push Token from the user's device. What's next? The most critical step is getting that token off the client and onto a server you control.

Sending notifications directly from the app itself is a definite no-go. It's insecure and just won't work for real-world applications where you need to trigger messages based on events happening elsewhere. Your backend is the command center for this whole operation. It’s where you’ll securely store these tokens and decide when to fire off a notification.

The basic flow is pretty simple: your React Native app sends the push token to your backend, which then saves it in a database. You’ll almost always want to link this token to a specific user ID. That's how you send targeted notifications later.

When something interesting happens, say, a user gets a new message or an order status changes, your server looks up that user's token and makes an API call to Expo’s push notification service.

This diagram nails it: a token only gets generated after the user gives their blessing. That permission prompt is a make-or-break moment in your user experience, so handle it with care.

Storing Tokens With Supabase

For a lot of us, a Backend-as-a-Service (BaaS) like Supabase is the fastest way to get a scalable backend running. It’s perfect for managing user data, including push tokens. If you're new to the concept, this article on what a Backend-as-a-Service is is a great primer.

First things first, you'll need a dedicated table in your Supabase project. A simple schema could look something like this:

  • id: uuid (Primary Key). A unique ID for the database record.

  • user_id: uuid (Foreign Key). Links the token to your main users table.

  • push_token: text (Unique). The actual Expo Push Token string itself.

  • created_at: timestamp. Handy for knowing when the token was added.

Back in your React Native app, once you've grabbed the token, you'll make an API call to save it.

// Example function inside your React Native app import { supabase } from './supabaseClient';

async function sendTokenToServer(userId, token) {

const { data, error } = await supabase

.from('push_tokens')

.upsert({ user_id: userId, push_token: token }, { onConflict: 'push_token' });


if (error) {

console.error('Error saving push token:', error);

} else {

console.log('Push token saved successfully:', data);

}

}

That upsert function is a real lifesaver here. It tries to insert a new row, but if a token already exists, it just updates the record (or does nothing, depending on your setup), neatly preventing duplicate entries.


Simplifying With CatDoes Cloud

If you're using an integrated platform, a lot of this backend grunt work can be automated. With CatDoes Cloud, for example, the whole process is much simpler. The platform's AI agents can set up the database tables and serverless functions for you based on a simple conversation.

  • Automated Table Creation: Just tell it, "I need to store push tokens for my users," and the system will generate the right schema for you.

  • Pre-built Functions: CatDoes provides managed edge functions already wired up to handle token storage and talk to the Expo API. This means less boilerplate code for you to write and maintain.

This approach is a massive advantage for teams trying to launch fast, letting you focus on your app's unique features instead of the plumbing.

Triggering Notifications With A Node.js Script

Now for the fun part: actually sending a notification. This is done by making a POST request to Expo's Push API endpoint. Here’s a basic Node.js script you can adapt for your backend. You could run this as a standalone service, but more often, you’d deploy it as a serverless function that gets triggered by events in your app.

// A simple Node.js script to send a push notification // Install 'node-fetch' with: npm install node-fetch@2

const fetch = require('node-fetch');

async function sendPushNotification(expoPushToken, title, body, data) {

const message = {

to: expoPushToken,

sound: 'default',

title: title,

body: body,

data: data, // e.g., { screen: 'Messages' }

};


await fetch('https://exp.host/--/api/v2/push/send', {

method: 'POST',

headers: {

'Accept': 'application/json',

'Accept-encoding': 'gzip, deflate',

'Content-Type': 'application/json',

},

body: JSON.stringify(message),

});

}


// Example usage:

const userToken = 'ExponentPushToken[xxxxxxxxxxxxxxxxxxxxxx]'; // The token from your database

sendPushNotification(

userToken,

'New Message!',

'You have received a new message from Jane Doe.',

{ customData: 'goes here' }

);


Important Takeaway: Never, ever hardcode push tokens in your backend code. This script is just a template. In a real application, you must fetch the userToken dynamically from your database based on which user you want to notify.

Push notifications are a powerful tool, but their effectiveness depends heavily on strategy and platform nuances. Understanding the landscape can help you fine-tune your approach.

Push Notification Engagement By Platform And Strategy

This table breaks down some key metrics that show how users on iOS and Android engage with notifications differently, and how certain strategies can boost interaction.

Metric

iOS

Android

Global Average / Impact

Opt-in Rate

43.9%

91.1%

67.5%

Click-Through Rate (CTR)

4.6%

10.2%

7.8%

Impact of Rich Media

+56% CTR lift

+25% CTR lift

Images/GIFs dramatically improve engagement.

Optimal Weekly Frequency

1-2 messages

3-5 messages

Exceeding this often leads to opt-outs.

The huge gap in opt-in rates between iOS and Android is the most striking takeaway. iOS users must explicitly agree to receive notifications, while Android users are opted-in by default. This makes that initial permission request on iOS absolutely crucial. These numbers show why a solid, well-thought-out backend strategy for push notifications in React Native is non-negotiable.

Advanced Techniques And Common Pitfalls To Avoid

Once you have the basic notification flow working, the real magic begins. You can move beyond simple alerts and start creating interactive experiences that guide users seamlessly through your app. This is where you elevate your push notifications in React Native from mere announcements to powerful engagement tools.

The goal is to make the user’s journey from notification to in-app content completely frictionless. A tap should feel like a natural extension of the message, not a jarring interruption.

Handling Notification Taps And Deep Linking

When a user taps a notification, they shouldn't just be dropped onto your app's home screen. That's a huge missed opportunity. Instead, you can direct them to a specific product page, a chat screen, or a new piece of content. This practice is known as deep linking.

To make this happen, you'll need to include extra data in your notification payload. This data is invisible to the user but tells your app exactly where to navigate once it opens.

Here’s a look at what the notification payload from your backend might look like:

{

"to": "ExponentPushToken[xxxxxxxxxxxxxxxxxxxxxx]",

"title": "Your Order Has Shipped!",

"body": "Track your package in the app.",

"data": {

"screen": "OrderDetails",

"orderId": "123-456-789"

}

}


Then, in your React Native app, you need to set up listeners to react to these taps. The expo-notifications library gives us a couple of helpful hooks for this.

  • useLastNotificationResponse(): This hook grabs the notification that the user tapped to open your app from a killed state. You'll want to check this when your app first loads.

  • addNotificationResponseReceivedListener(): This listener fires when the user taps a notification while your app is already running in the background.

By combining these two, you can reliably capture taps and use a library like React Navigation to route the user to the correct screen.

Implementing Rich Notifications

Standard text notifications get the job done, but rich notifications with images, GIFs, and action buttons are far more engaging. It’s not just a hunch. Studies consistently show that notifications containing media can boost click-through rates by over 50%.

Adding an image is as simple as including an attachment URL in your notification payload when sending it to the Expo Push API. For interactive buttons, you can define categories of actions.

For instance, you might create a "new_message" category with "Reply" and "Mark as Read" buttons. When a user taps one, your app receives an event with their choice, allowing you to build more complex interactions right from the notification itself.

Your primary goal with any notification is to provide immediate value. Rich notifications and deep linking are not just fancy features; they are direct paths to delivering that value, which in turn builds user trust and boosts retention.

Troubleshooting Common Problems

Even with a perfect setup, things can go wrong. Debugging push notifications can be tricky because so many different systems are involved: your app, Expo’s services, APNs, and FCM. Here are some of the most common issues developers run into.

The Dreaded Missing Token

One of the most frequent hangups is the push token not being generated. Before you spend hours debugging your code, run through this quick checklist:

  • Are you on a real device? This is non-negotiable. iOS simulators and Android emulators cannot generate valid push tokens.

  • Did the user grant permission? If finalStatus in your permission request isn't 'granted', you're not getting a token. Simple as that.

  • Is your app.json configured correctly? Double-check your Firebase google-services.json path for Android and your projectId for Expo Application Services (EAS). A small typo here can break everything.

Notifications Sent But Not Received

This is another classic head-scratcher. Your server gets a success response from the Expo API, but nothing shows up on the device.

  • Check your app state: By default, notifications don't appear if the app is in the foreground. You have to use Notifications.setNotificationHandler to define how they should behave when the app is open.

  • APNs environment mismatch: For iOS, make sure the aps-environment in your app.json is set to development for development builds and production for builds submitted to TestFlight or the App Store. A mismatch here will cause notifications to fail silently.

  • Android Channel Configuration: On Android 8.0+ (API 26), notifications must be assigned to a channel. If you send a notification without specifying a valid channel, it may not be displayed. It's best practice to create your channels upfront.

Common Questions & Sticking Points

Getting push notifications right in React Native often feels like navigating a minefield. You follow the docs, everything seems correct, but then nothing happens. This section tackles the most common questions and sharp edges that trip developers up, giving you clear, direct answers so you can get unstuck and ship.

We'll cover everything from testing limitations to mysterious missing tokens. Think of this as the troubleshooting guide I wish I had when I first started.

Can I Test Expo Push Notifications on a Simulator or Emulator?

This is easily the number one question, and the answer is a hard no. You absolutely must use a physical device, a real iPhone or Android phone, to get the end-to-end push notification flow working.

Simulators and emulators are great for UI work, but they can't request a legitimate push token from Apple's or Google's servers. Since an Expo Push Token is built on top of these native tokens, trying to generate one on a simulated device will always fail. Save yourself hours of frustration and plug in a physical device from the start.

Why Isn't My Expo Push Token Generating?

Okay, so you're on a real device, but you're still not getting a token. What gives? It almost always comes down to one of these usual suspects. Running through this checklist usually solves it in minutes.

  • Did the user say no? The most common culprit is that the user denied the notification permission prompt. Your code needs to gracefully handle this. Always check the permission status before you try to get a token.

  • Is your app.json configured correctly? On Android, a missing or misconfigured google-services.json file is a guaranteed dealbreaker. For iOS, double-check that your EAS projectId is present and correctly passed into the getExpoPushTokenAsync call.

  • Are the motherships online? It's rare, but sometimes the problem isn't you. There could be a temporary issue with Apple Push Notification service (APNs) or Firebase Cloud Messaging (FCM).

How Do I Handle a Notification When My App Is Open?

By default, iOS and Android won't show a notification banner if the app is already in the foreground. This is by design because you don't want to bombard a user who is actively using your app.

To change this, you have to tell the OS what to do. The expo-notifications library gives you the perfect tool for this: Notifications.setNotificationHandler.

Inside this handler, you call the shots. You can force the OS to show the alert, play a sound, or update the badge count. A popular alternative is to kill the system alert entirely and show your own custom in-app banner or toast instead, which often provides a much better user experience.

Do I Really Need a Backend to Send Push Notifications?

Yes, for any serious application, a backend isn't optional, it's essential. While you can use tools like Expo's Push Notification Tool to send one-off test messages, that’s not a scalable or secure way to run a production app.

Your backend plays two crucial roles:

  1. It’s the vault for user push tokens. You need a secure database to store tokens and link them to the right user accounts.

  2. It’s the brain of your notification logic. Your server is what decides when and why a notification gets sent, whether it's triggered by a new message, a status update, or a marketing campaign.

Trying to manage this from the client side is a massive security risk and would cripple your app's potential.

Tired of wrestling with backend configurations just to get your app off the ground? The AI agents at CatDoes can build your production-ready React Native app from a simple description, complete with a fully managed backend for push notifications, auth, and databases. Get started for free and launch your app without the boilerplate.

Writer

Nafis Amiri

Co-Founder of CatDoes