Skip to main content
Tutorial

Push Notifications in Expo: The Complete Guide I Wish Existed When I Started

4 min read

Real talk about Expo push notifications, including permissions, time zones, and production issues

react-nativeexpopush notificationstutorialjavascript

I spent 8 straight hours trying to make push notifications work in a React Native app for a client in Sharjah last year. The app was for a plant care service — they wanted reminders to hit users at the exact time their aloe vera needed watering. Simple, right? No. Expo’s SDK was throwing errors I’d never seen, iOS permissions were a lottery, and the client kept asking (in Arabic, which I’m fluent in) why the flowers kept dying even though the app was live.

It’s 2026 now, and I’ve finally figured out a workflow that works. Let’s fix what the Expo docs don’t.


Getting Started: What You Need Before Writing One Line

The good news? Setting up push notifications in Expo isn’t magic. The bad news? It’s not a checkbox item either. You’ll need:

  1. A managed Expo project running SDK 49 or higher (I use 54 for Greeny Corner)
  2. Android/iOS credentials (we’ll dive into this later)
  3. Patience. Especially for iOS — Apple’s notification system is built to test your soul.

Install expo-notifications first. This isn’t optional, and yes — you’ll import it even if you’re using Firebase downstream. They’re not the same thing.

bash
expo install expo-notifications

Step 1: Handle Permissions — The Confusing Part

Start by requesting permissions right after app launch. Most tutorials skip this, then wonder why users get a prompt 30 seconds later (spoiler: they don’t).

I put this in my root layout provider:

javascript
useEffect(() => {
  registerForPushNotificationsAsync();
}, []);

async function registerForPushNotificationsAsync() {
  const { status } = await Notifications.requestPermissionsAsync();
  if (status !== 'granted') {
    alert('Enable notifications to keep your plants alive!');
    return;
  }
}

This isn’t a guarantee they’ll stay granted. I had a Qatari client’s CEO uninstall the app after notifications stopped working — turned out he’d hit “don’t allow” during a demo. You’ll need to check permission status regularly.


Step 2: Generate the Push Token — Not as Easy as It Sounds

Call getDevicePushTokenAsync() from expo-notifications:

javascript
import * as Notifications from 'expo-notifications';

export async function getExpoPushToken() {
  const pushToken = await Notifications.getDevicePushTokenAsync();
  return pushToken.data;
}

Hold up — this will return garbage if you’re not using a real device. Simulator? Fakes tokens. Web testing? Don’t bother.

Here’s what happened to me: built a proof of concept for a UAE real estate app (Reach Home Properties) on Expo web. Everything looked good. Deployed to clients. iOS users got nothing. The token validation was client-side. Don’t do that.


Dev vs Production: Why Does It Work in Metro But Not Standalone?

Ah yes, the classic “works in dev, fails in prod” issue. Your app.json needs extra sprinkles:

json
{
  "expo": {
    "name": "Greeny Corner",
    "plugins": [
      [
        "expo-build-properties",
        {
          "ios": {
            "supports32Bit": false,
            "requireFullScreen": true
          }
        }
      ]
    ]
  }
}

iOS requires supports32Bit set to false for push tokens to work. I spent two days on a limo booking app (Tawasul Limo) before finding this buried in an Apple dev forum. Don’t let that be you.


Sending Notifications: Testing Without Losing Your Mind

Don’t use expo-cli commands. They’re deprecated. Use Expo’s API or a service like Courier.

bash
curl -H "Content-Type: application/json" -X POST "https://exp.host/--/api/v2/push/send" -d '{
  "to": "ExponentPushToken[XXXXXXXXXXXXXXXXXXXXXX]",
  "title": "Hey there",
  "body": "Water your aloe vera",
  "data": {"plantId": "123"}
}'

Wait — your token starts with ExponentPushToken? Good. If it starts with ExpoPushToken, you’re in Expo Go and it won’t work.

Test with Expo Notifier. It’s in Apple’s App Store because Expo made this tool. Use it. Save your sanity.


Handling Time Zones in the UAE

A client in Dubai once asked why their users got push reminders at 3 AM. Turns out, the server was sending timestamps in UTC.

Here’s how to respect local time zones in notifications:

javascript
const trigger = new Date();
trigger.setHours(9); // 9 AM local time

Notifications.scheduleNotificationAsync({
  content: { title: 'Water your plant' },
  trigger
});

If your users span the GCC, always store their current time zone and adjust triggers accordingly. This isn’t built-in — you’ll have to calculate offsets manually. Fun.


One Last Gotcha: Notifications Vanish on iOS When the App is Fully Closed

Yep, Apple kills background processes aggressively. The fix? You’ll need to set up a VoIP push. It’s involved and out of scope here, but I wrote a thread on Twitter about it if you’re pulling out your hair on a Wednesday (which you will be).


If you’re building something in Expo now, don’t expect perfection. When Greeny Corner launched, notifications failed for 15% of users after we changed the app icon. We had to manually resubmit the new asset to Expo’s servers. No error showed up — we found it by comparing MD5 hashes of config files.

This isn’t a perfect system. It’s a system that works if you remember to care about the edge cases.

Need help shipping this? Hit me up at sarahprofile.com/contact. I’m probably wrestling with Firebase Cloud Messaging settings for another limo booking app — but I’ll answer. 😊

S

Sarah

Senior Full-Stack Developer & PMP-Certified Project Lead — Abu Dhabi, UAE

7+ years building web applications for UAE & GCC businesses. Specialising in Laravel, Next.js, and Arabic RTL development.

Work with Sarah