[Expo / ReactNative] 4 steps to run PUSH notifications on iOS simulator

Mobile application development is a way faster when you use simulators/emulators. Nowadays, you can test nearly all major features of mobile phone API, without running application on a real device. It’s very helpful and speeds up the whole process. Some time ago Apple released a new version of Xcode with a new, shiny simulator. And thanks to that, we can now test our PUSH notifications without a real device! Even on applications built with Expo. Check out how!

Step 1: Update your Xcode

The most important step is to update Xcode to version 11.4+. It contains a new version of a simulator with a vary of new features - including PUSH notifications.

More info about this version you can find in this release note.

Step 2: Prepare your app for receiving PUSHes

If you don’t have an app yet, we’ll prepare simple example now, based on the latest documentation of Expo. There you can find, how to set up project. The most important part for us is to set listeners on PUSH notifications. We can do this in the same way, how we configure listeners for a standalone app. So, in our App.js we have to add something like that (the original code you can find in documentation):

import React from 'react';
import { Text, View, Button, Vibration, Platform } from 'react-native';
import { Notifications } from 'expo';
import * as Permissions from 'expo-permissions';

export default class AppContainer extends React.Component {
  state = {
    notification: {},

  componentDidMount() {
    this._notificationSubscription = Notifications.addListener(this._handleNotification)

  registerForPushNotificationsAsync = async () => {
    const { status: existingStatus } = await Permissions.getAsync(Permissions.NOTIFICATIONS)
    let finalStatus = existingStatus
    if (existingStatus !== 'granted') {
      const { status } = await Permissions.askAsync(Permissions.NOTIFICATIONS)
      finalStatus = status
    if (finalStatus !== 'granted') {
      alert('Failed to get push token for push notification!')

  _handleNotification = notification => {
    this.setState({ notification })

  render() {
    return (
          flex: 1,
          alignItems: 'center',
          justifyContent: 'space-around',
        <View style={{ alignItems: 'center', justifyContent: 'center' }}>
          <Text>Origin: {this.state.notification.origin}</Text>
          <Text>Data: {JSON.stringify(this.state.notification.data)}</Text>

As you may see, we change the original code a bit; We don’t need ExpoNotificationToken (we cannot get it on simulators, anyway), and we only set listeners and ask for permissions for notifications.

Such prepared application we can run on our iOS simulator.

Step 3: Prepare notification file

The key part is to prepare a specific .apns file. On the Internet, you can find a lot of tutorials, how to run notifications on iOS simulator, but we will focus on a specific case: Expo wrapped applications.

Let’s create notification-test.apns file and put such content inside:

    "Simulator Target Bundle": "host.exp.Exponent",
    "aps": {
        "alert": {
            "title": "EXPO iOS Simulator PUSH",
            "body": "It works!"
    "experienceId": "@rduraj/example_app",
    "body": {
        "type": "IOS_PUSH",
        "example": {
          "exampleField": "text"

Let’s go through the most important parts of it:

  • Simulator Target Bundle - Bundle ID of Expo client app on your simulator, because Expo Client is the main recipient and it will broadcast notification to our app.
  • aps.alert.* - object with notification details, which will be used by the system, to display proper notification bubble, set badge number, etc.
  • body - this object will be delivered as our notification data
  • experienceId - the most important property; Expo Client, based on this information, runs a specific app and provides notification data to it. In this example, it points to app on my account. experienceId is a mix of @{expo_username}/{app_slug}, app_slug you can find in app.json file inside your project.

Step 4: Send notification!

Important: You have to be logged in both: CLI and Expo client app on your simulator.

As I mentioned, there are a couple of ways to run notifications on a simulator. Personally, I prefer CLI way. On your terminal, you just have to run one command:

$ xcrun simctl push booted ./notification-test.apns

booted in this command means, that this notification will be delivered to the current running simulator. If you want to choose a specific one, you can list all of the simulators by command:

$ xcrun simctl list

and replace booted by a particular device ID.

After that, you should see your notification on the simulator and the app should be shown after tapping on this bubble (if you have Expo Client in the background, vide: Caveats).

Expo PUSH notifications on iOS Simulator


We have to say this loud: simulator’s notifications are just simulation of real notifications and we have to treat them like that. They are great for styling internal notifications or checking behaviors. But we have to keep in mind, that this method is not sufficient enough to replace tests on real devices;

To get the most from simulating notifications, you have to remember about some rules:

  • Be logged in - it’s easy to forget, that you have to be logged in through Expo CLI, before you run yarn start and you have to log in to Expo Client, right after the first run of a client on your simulator;
  • Keep Expo Client open - if you want to check, how your app behaves, after receiving PUSH when it’s not active, you have to keep Expo Client open. To do this, you need to close your app and manually open Expo Client on your simulator.

That’s all. I hope you will find this method useful, speeds up your work and makes it more comfortable!

Happy testing!