# Expo SDK Documentation

Generated on: 4/17/2026

This documentation bundle contains the complete Expo SDK documentation for use with LLMs and offline reference.

---

## Quick start guide

**Source:** /integrations/sdk/expo/quick-start-guide

# Quick Start Guide

 Our MCP server can help you get started

Our MCP server includes SDK-installation tools that can help you get integrated quickly with Customer.io and troubleshoot any issues you might have. See [Set up an MCP server](/ai/mcp-server/) to get started.

## Setup process overview[](#setup-process-overview)

Expo provides a managed framework to help you build React Native mobile apps. Our Expo plugin relies on our React Native SDK. When you run the prebuild, we’ll generate the native files you’ll need to integrate Customer.io push notifications in your app. Then you’ll add calls to your app to `identify` people, `track` their activity, and listen for notifications.

1.  [Install the SDK.](#install)
2.  [Identify and Track](#identify-and-track)
3.  [Push Notifications](#push-notifications)
4.  [In-App](#in-app)

## 1\. Install the SDK[](#install)

1.  If you haven’t already gotten a *CDP API key*, you’ll need to add a new *Expo* integration to your Customer.io workspace. This “integration” represents your app in Customer.io and provides you the CDP API key that you’ll use to initialize your app. See [Get your CDP API key](../getting-started/auth/#get-your-cdp-api-key) for details.
    
2.  Install the Customer.io Expo plugin and React Native SDK.
    
    ```bash
    npx expo install customerio-expo-plugin customerio-reactnative
    ```
    
3.  Configure your *CDP API key* and *site ID*.
    
    *   **CDP API Key**: You’ll find this key in the **[*Expo* integration](https://fly.customer.io/workspaces/last/journeys/integrations/all/overview)** that you created in [step 1 of this section](#install).
    *   **Site ID**: You’ll find this value in your integrations under **[Customer.io API: Track](https://fly.customer.io/workspaces/last/settings/api_credentials)**.
4.  Initialize React Native SDK
    
     Auto-initialization (Expo 53+)
    
    #### Auto-initialization (Expo 53+)[](#Auto-initialization \(Expo 53+\))
    
    **Note:** Auto-initialization is supported in Expo 53+ only. For older Expo versions, use manual initialization.
    
    Add React Native SDK configuration to your `app.json` file:
    
    ```json
    {
       "expo": {
          "plugins": [
             [
                "customerio-expo-plugin",
                {
                   "config": {
                      "cdpApiKey": "<CDP API KEY>",
                      "siteId": "<SITE ID>", // Optionally, you can enable in-app messaging by adding site ID
                      "region": "us" // use "eu" if your workspace is in EU region
                   }
                }
             ]
          ]
       }
    }
    ```
    
    See [Configuration Options](../getting-started/packages-options/#auto-initialization-configuration-options-expo-53) for more details.
    
     Manual initialization
    
    #### Manual initialization[](#Manual initialization)
    
    Add your *CDP API key* and *site ID* to your `.env` file.
    
    ```env
    EXPO_PUBLIC_CDP_API_KEY=<CDP API KEY>
    EXPO_PUBLIC_SITE_ID=<SITE ID>
    ```
    
    Ensure the SDK is initialized in your app. You can call the `initialize` method from your components or services. We recommend initializing the SDK in the `useEffect` hook of your main component or layout file.
    
    ```typescript
    import { CioConfig, CustomerIO } from "customerio-reactnative";
    
    useEffect(() => {
       const initializeCustomerIO = () => {
          const config: CioConfig = {
             cdpApiKey: process.env.EXPO_PUBLIC_CDP_API_KEY,
             region: "us", // use "eu" if your workspace is in EU region
             // Optionally, you can enable in-app messaging by adding the site ID
             inApp: {
                siteId: process.env.EXPO_PUBLIC_SITE_ID,
             }
          };
          CustomerIO.initialize(config);
       };
       initializeCustomerIO();
    }, []);
    ```
    
5.  You must also add push notification options to your `app.json` file to build the native files, even if you don’t use Customer.io push notifications in your app. See [Push Notifications](#push-notifications) for more information, and then run the prebuild command.
    
    ```bash
    npx expo prebuild
    ```
    
6.  Run your application using `npx expo run:ios` or `npx expo run:android` instead of using the dev server via `npx expo start`. You need to use these commands so that the plugin can build native files that let your app receive push notifications and in-app messages.
    

## 2\. Identify and Track[](#identify-and-track)

1.  Identify a user in your app using the `CustomerIO.identify` method. You *must* identify a user before you can send push notifications and personalized in-app messages.
    
    ```typescript
    import { CustomerIO } from "customerio-reactnative";
    
    const identifyUserExample = () => {
       CustomerIO.identify({
          userId: 'expo-test-user@example.com',
          traits: {
             firstName: 'John',
             lastName: 'Doe',
             email: 'expo-test-user@example.com',
             subscriptionStatus: 'active',
          },
       });
       console.log('User identified successfully');
    };
    ```
    
2.  Track a custom event using the `CustomerIO.track` method. Events help you trigger personalized campaigns and track user activity.
    
    ```typescript
    import { CustomerIO } from "customerio-reactnative";
    
    const trackCustomEventExample = () => {
       CustomerIO.track('purchased_item', {
          product: 'Premium Subscription',
          price: 99.99,
          currency: 'USD'
       });
       console.log('Custom event tracked successfully');
    };
    ```
    
3.  Track screen views to automatically trigger in-app messages associated with specific screens.
    
    ```typescript
    import { CustomerIO } from "customerio-reactnative";
    
    const trackScreenViewExample = () => {
       CustomerIO.screen('ProductDetails');
       console.log('Screen view tracked successfully');
    };
    ```
    

## 3\. Push Notifications[](#push-notifications)

1.  Configure push notifications for iOS and Android. Update your `app.json` file with the following configuration:
    
     Auto-initialization
    
    #### Auto-initialization[](#Auto-initialization)
    
    ```json
    {
       "expo": {
          ...Other options
          "plugins": [
             [
                "customerio-expo-plugin",
                {
                   "config": {
                      "cdpApiKey": "<CDP API KEY>",
                      "region": "us" // us or eu
                   },
                   "android": {
                      "googleServicesFile": "./files/google-services.json"
                   },
                   "ios": {
                      "pushNotification": {
                         "useRichPush": true
                      }
                   }
                }
             ]
          ]
       }
    }
    ```
    
     Manual initialization
    
    #### Manual initialization[](#Manual initialization)
    
    ```json
    {
       "expo": {
          ...Other options
          "plugins": [
             [
                "customerio-expo-plugin",
                {
                   "android": {
                      "googleServicesFile": "./files/google-services.json"
                   },
                   "ios": {
                      "pushNotification": {
                         "useRichPush": true,
                         "env": {
                            "cdpApiKey": "<CDP API KEY>",
                            "region": "us" // us or eu
                         }
                      }
                   }
                }
             ]
          ]
       }
    }
    ```
    
2.  Upload your push service credentials in Customer.io under **[Settings > Workspace Settings > Push](https://fly.customer.io/workspaces/last/settings/actions/push/ios)**.
    
    *   **iOS**: upload your Apple Push Notification certificate (`.p8` file).
    *   **Android**: upload your server key json file (`.json` format).

See [Push Notifications](../push-notifications/) for detailed configuration options.

## 4\. In-App[](#in-app)

1.  To enable in-app messaging, all you need to do is add the site ID. Remember, you’ll find your site ID under **[Data & Integrations > Integrations > Customer.io API: Track](https://fly.customer.io/workspaces/last/settings/api_credentials)** in the *Connections* tab.
    
2.  Ensure that the SDK is initialized with the site ID in your app:
    
     Auto-initialization
    
    #### Auto-initialization[](#Auto-initialization)
    
    Add `siteId` to your `app.json` config:
    
    ```json
    {
       "expo": {
          "plugins": [
             [
                "customerio-expo-plugin",
                {
                   "config": {
                      "cdpApiKey": "<CDP API KEY>",
                      "siteId": "<SITE ID>",
                      "region": "us"
                   }
                }
             ]
          ]
       }
    }
    ```
    
     Manual initialization
    
    #### Manual initialization[](#Manual initialization)
    
    Add `siteId` to your environment variables and initialization config. You can call the `initialize` method from your components or services:
    
    ```env
    EXPO_PUBLIC_SITE_ID=<SITE ID>
    ```
    
    ```typescript
    import { CioConfig, CustomerIO } from "customerio-reactnative";
    import { useEffect } from "react";
    
    useEffect(() => {
       const initializeCustomerIO = () => {
          const config: CioConfig = {
             cdpApiKey: process.env.EXPO_PUBLIC_CDP_API_KEY,
             inApp: {
                siteId: process.env.EXPO_PUBLIC_SITE_ID,
             }
          };
    
          CustomerIO.initialize(config);
       };
       initializeCustomerIO();
    }, []);
    ```
    

See [In-App Messages](../in-app-messages/) for more details.

---

## Getting started > Auth

**Source:** /integrations/sdk/expo/getting-started/auth

# Authentication

To use the SDK, you’ll need two kinds of API keys: A *CDP API Key* to send data to Customer.io and a *Site ID*, telling the SDK which workspace your messages come from.

These keys come from different places in Customer.io!

1.  **CDP API Key**: You’ll get this key when you [set up your mobile app as a data-in integration](#set-up-a-new-source) in Customer.io.
2.  **Site ID**: This key tells the SDK which workspace your in-app messages come from. You’ll use it to support `inApp` messages. If you’re upgrading from a previous version of the Customer.io SDK, it also serves as the `migrationSiteId`.

## Get your CDP API Key[](#get-your-cdp-api-key)

You’ll use your write key to initialize the SDK and send data to Customer.io; you’ll get this key from your Expo entry under **[Data & Integrations > Integrations](https://fly.customer.io/workspaces/last/integrations/all/overview)**. If you don’t see your app on this page, you’ll need to [add a new integration](#set-up-a-new-source).

1.  Go to *Data & Integrations* > *Integrations*.
2.  Go to the **Connections** tab and find the entry for your Expo app. If you don’t see Expo in the list of connections, you’ll need to [add a new integration](#set-up-a-new-source).
    
    [![the connections page, showing an expo integration connected to a journeys destination](https://customer.io/images/cdp-expo-connected-destination.png)](#f2bced1bae1c2dc066a212a83b2d4535-lightbox)
    
3.  Go to **Settings** and find your **API Key**. Copy this key and use it in your app configuration.
    
    [![get your CDP API Key from your connection's settings page](https://customer.io/images/cdp-expo-source-api-key.png)](#8f9b400c6f20a640832e730b87073f21-lightbox)
    

 Auto-initialization (Expo 53+)

#### Auto-initialization (Expo 53+)[](#Auto-initialization \(Expo 53+\))

Add the key to the `config` object in your `app.json` file:

```json
{
   "expo": {
      "plugins": [
         [
            "customerio-expo-plugin",
            {
               "config": {
                  "cdpApiKey": "<CDP_API_KEY>",
                  "region": "us", // use "eu" if in EU region
                  "siteId": "<SITE_ID>", // Required for in-app messaging
                  "migrationSiteId": "<OLD_SITE_ID>" // Required if migrating from an earlier version
               },
               "android": {
                  "googleServicesFile": "./files/google-services.json",
                  "setHighPriorityPushHandler": true
               },
               "ios": {
                  "pushNotification": {
                     "useRichPush": true
                  }
               }
            }
         ]
      ]
   }
}
```

 Manual initialization

#### Manual initialization[](#Manual initialization)

Add the key to the `customerio-expo-plugin` object in your `app.json` file and your app’s code:

**In app.json:**

```json
{
   "expo": {
      "plugins": [
         [
            "customerio-expo-plugin",
            {
               "android": {
                  "googleServicesFile": "./files/google-services.json",
                  "setHighPriorityPushHandler": true
               },
               "ios": {
                  "pushNotification": {
                     "useRichPush": true,
                     "env": {
                        "cdpApiKey": "<CDP_API_KEY>",
                        "region": "us"
                     }
                  }
               }
            }
         ]
      ]
   }
}
```

**In your app’s code:**

```typescript
import { CioRegion, CustomerIO, CioConfig } from 'customerio-reactnative';

const App = () => {
   useEffect(() => {
      const config: CioConfig = {
         cdpApiKey: 'CDP API Key', // Mandatory
         migrationSiteId: 'siteId', // Required if migrating from an earlier version
         region: CioRegion.US,
         inApp: {
            siteId: '<your_site_id>',
         },
      };
      CustomerIO.initialize(config);
   }, []);
};
```

## Add a new integration[](#set-up-a-new-source)

If you don’t already have a write key, you’ll need to set up a new connectionRepresents an integration between Customer.io and another service or app under *[Data & Integrations > Integrations](https://fly.customer.io/workspaces/last/integrations/all/overview)*. A connection in Customer.io provides you with API keys and settings for your integration.. The connection represents your app and the stream of data that you’ll send to Customer.io.

1.  Go to [**Data & Integrations** > **Integrations** and click the **Directory** tab](https://fly.customer.io/workspaces/last/journeys/integrations/all/directory).
2.  Find and select the **Expo** integration.
    
    [![The integration directory showing the expo integration](https://customer.io/images/expo-setup.png)](#82e5ea2130241fe735490e835b429c0a-lightbox)
    
3.  Enter a *Name* for the integration, like “My Expo App”.
4.  We’ll present you with a code sample containing a `cdpApiKey` that you’ll use to initialize the SDK. Copy this key and keep it handy. You’ll use it to initialize the SDK in your Expo app.
5.  (Optional) Set up your Expo app and click *Test Connection*.
6.  If you tested your connection first, click **Complete Setup**. Otherwise, click **Save & Complete Later**.
    
    [![The expo setup page with the integration named 'My Expo App'](https://customer.io/images/expo-find-in-directory.png)](#062f18a2b6c50bcd8d89c22c0a7f3630-lightbox)
    

## Get your Site ID[](#get-your-site-id)

You’ll use your site ID with the `inApp` option to support in-app messaging.

And if you’re upgrading from a previous version of the SDK, you’ll also use your site ID as your `migrationSiteId`. This key is used to send remaining tasks to Customer.io when your audience updates your app.

1.  Go to Settings > **Workspace Settings** in the upper-right corner of the Customer.io app and go to [**API and Webhook Credentials**](https://fly.customer.io/workspaces/last/settings/api_credentials).
    
2.  Copy the **Site ID** for the set of credentials that you want to send your in-app messages from. If you don’t have a set of credentials, click **Create Tracking API Key**.
    
    [![find your site ID](https://customer.io/images/cdp-js-site-id.png)](#64d2b27827ffddb00dc77b851a7a6854-lightbox)
    
3.  Use this Site ID to enable in-app messaging:
    

 Auto-initialization (Expo 53+)

#### Auto-initialization (Expo 53+)[](#Auto-initialization \(Expo 53+\))

Add the Site ID to your `app.json` configuration:

```json
{
   "expo": {
      "plugins": [
         [
            "customerio-expo-plugin",
            {
               "config": {
                  "cdpApiKey": "<CDP_API_KEY>",
                  "region": "us",
                  "siteId": "<SITE_ID>"
               }
            }
         ]
      ]
   }
}
```

 Manual initialization

#### Manual initialization[](#Manual initialization)

Use the Site ID in your app’s code to initialize the `inApp` package:

```typescript
import {
  CioLogLevel, CioRegion, CustomerIO, CioConfig
} from 'customerio-reactnative';

const App = () => {

useEffect(() => {
   const config: CioConfig = {
      cdpApiKey: 'CDP API Key', // Mandatory
      migrationSiteId: 'siteId', // Required if migrating from an earlier version
      region: CioRegion.US,
      logLevel: CioLogLevel.Debug,
      trackApplicationLifecycleEvents: true,
      inApp: {
         siteId: 'site_id',
      },
   };
   CustomerIO.initialize(config)
   }, [])
}
```

## Securing your credentials[](#securing-your-credentials)

To simplify things, code samples in our documentation sometimes show API keys directly in your code. But you don’t have to hard-code your keys in your app. You can use environment variables, management tools that handle secrets, or other methods to keep your keys secure if you’re concerned about security.

To be clear, the keys that you’ll use to initialize the SDK don’t provide read access to data in Customer.io; they only write data to Customer.io. A bad actor who found your credentials can’t use your keys to read data from our servers.

---

## Getting started > How it works

**Source:** /integrations/sdk/expo/getting-started/how-it-works

# How it works

Before you can take advantage of our SDK, you need to install the module(s) you want to use, initialize the SDK, and understand the order of operations.

Our SDKs provide a ready-made integration to identify people who use mobile devices and send them notifications. Before you start using the SDK, you should understand a bit about how the SDK works with Customer.io.

Before a person logs into your app, any activity they perform is associated with an anonymous person in Customer.io. In this state, you can track their activity, but you can’t send them messages through Customer.io.

When someone logs into your app, you’ll send an `identify` call to Customer.io. This makes the person eligible to receive messages and reconciles their anonymous activity to their identified profile in Customer.io.

You send messages to a person through the Customer.io campaign builder, broadcasts, etc. These messages are not stored on the device side. If you want to send an event-triggered campaign to a mobile device, the mobile device user must be identified and have a connection such that it can send an event back to Customer.io and receive a message payload.

## Your app is a data source and Customer.io is a destination[](#your-app-is-a-data-source-and-customerio-is-a-destination)

Our SDK is a data inAn integration that feeds data *into* Customer.io. integration. It routes data from your app to both Customer.io *and* any other outbound services where you might use your mobile data. This makes it easy to use your app as a part of your larger data stack without using extra packages or code.

When you set up your app, you’ll integrate our SDK. But you’ll also determine where you want to route your data to—your Customer.io workspace *and* destinations outside of Customer.io.

[![the connections page, showing an android source connected to a journeys destination](https://customer.io/images/cdp-expo-connected-destination.png)](#f2bced1bae1c2dc066a212a83b2d4535-lightbox)

## Minimum requirements[](#minimum-requirements)

To support the Customer.io SDK, you must:

*   Use a version of Expo from 45 to (and including) 53.
    
*   Set iOS 13 or later as your minimum deployment target in XCode
    
*   Have an Android device or emulator with Google Play Services enabled and a minimum OS version between Android 5.0 (API level 21) and Android 13.0 (API level 33).
    
*   Have an iOS 13+ device to test your implementation. You cannot test push notifications in a simulator.
    

## The Processing Queue[](#the-processing-queue)

The SDK automatically adds all calls to a queue system, and waits to perform these calls until certain criteria is met. This queue makes things easier, both for you and your users: it handles errors and retries for you (even when users lose connectivity), and it can save users’ battery life by batching requests.

The queue holds requests until any one of the following criteria is met:

*   There are 20 or more tasks in the queue.
*   30 seconds have passed since the SDK performed its last task.
*   The app is closed and re-opened.

For example, when you identify a new person in your app using the SDK, you won’t see the created/updated person immediately. You’ll have to wait for the SDK to meet any of the criteria above before the SDK sends a request to the Customer.io API. Then, if the request is successful, you’ll see your created/updated person in your workspace.

---

## Getting started > Packages options

**Source:** /integrations/sdk/expo/getting-started/packages-options

# Packages and Configuration Options

## How it works[](#how-it-works)

With our Expo plugin, you can configure the SDK in two ways:

**Auto-initialization (Expo 53+):** Configure everything in your `app.json` file using the `config` object. The SDK will automatically initialize without requiring `CustomerIO.initialize()` in your app code.

**Manual initialization:** Configure native settings in `app.json` and SDK runtime settings in your React Native app code using `CustomerIO.initialize()`.

Both approaches require configuration in your `app.json` or `app.config.js` files that affect the native files we generate when you run the prebuild.

## Configuring the Expo Plugin[](#configuring-the-expo-plugin)

To use the Expo plugin, you’ll need to add a `customerio-expo-plugin` entry to your `app.json` or `app.config.js` files. Before you set up your app:

*   If you’re in our EU region, set the region to `eu`.
*   To support Customer.io’s latest features, you’ll also want to set your iOS deployment target to `15.1` or higher.

If you send notifications to iOS users through Firebase Cloud Messaging, you’ll need to:

*   Set `expo-build-properties.ios.useFrameworks` to `static`.
*   Set `customerio-expo-plugin.ios.pushNotification.provider` to `fcm`.
*   Set `customerio-expo-plugin.ios.pushNotification.googleServicesFile` to the location of your `GoogleService-Info.plist` file. If you use `@react-native-firebase` with the Customer.io SDK, you should not set this value [Learn more](/integrations/sdk/expo/push-notifications/multiple-push-providers)

 Auto-initialization (Expo 53+)

#### Auto-initialization (Expo 53+)[](#Auto-initialization \(Expo 53+\))

```json
{
  "plugins": [
    [
      "expo-build-properties",
      {
        "ios": {
          "deploymentTarget": "15.1", // set your iOS deployment target to support Customer.io
          "useFrameworks": "static" // set if you use FCM to send push notifications to iOS devices
        }
      }
    ],
    [
      "customerio-expo-plugin",
      {
        "config": {
          "cdpApiKey": "<cdpApiKey>",
          "siteId": "<siteId>",
          "region": "us"
        },
        "android": {
          "googleServicesFile": "./files/google-services.json"
        },
        "ios": {
          "useFrameworks": "static", // must match value in expo-build-properties if set
          "pushNotification": {
            "provider": "fcm", // use "apn" for Apple Push Notification service
            "googleServicesFile": "<Path to GoogleService-Info.plist file>", // required if provider is "fcm" and you don't use the @react-native-firebase SDK
            "useRichPush": true,
            "appGroupId": "group.com.example.myapp.cio"
          }
        }
      }
    ]
  ]
}
```

 Manual initialization

#### Manual initialization[](#Manual initialization)

```json
{
  "plugins": [
    [
      "expo-build-properties",
      {
        "ios": {
          "deploymentTarget": "15.1", // set your iOS deployment target to support Customer.io
          "useFrameworks": "static" // set if you use FCM to send push notifications to iOS devices
        }
      }
    ],
    [
      "customerio-expo-plugin",
      {
        "android": {
          "googleServicesFile": "./files/google-services.json"
        },
        "ios": {
          "useFrameworks": "static", // must match value in expo-build-properties if set
          "pushNotification": {
            "provider": "fcm", // use "apn" for Apple Push Notification service
            "googleServicesFile": "<Path to GoogleService-Info.plist file>", // required if provider is "fcm" and you don't use the @react-native-firebase SDK
            "useRichPush": true,
            "appGroupId": "group.com.example.myapp.cio",
            "env": {
              "cdpApiKey": "<cdpApiKey>",
              "region": "us"
            }
          }
        }
      }
    ]
  ]
}
```

### Auto-initialization configuration options (Expo 53+)[](#auto-initialization-configuration-options-expo-53)

When using auto-initialization, include a `config` object in your plugin configuration:

Option

Type

Default

Description

`config`

object

Container for auto-initialization settings. If present, enables auto-initialization.

`config.cdpApiKey`

string

**Required**: Your CDP API key

`config.siteId`

string

Optional: Your site ID for in-app messaging. Required to enable in-app messaging.

`config.region`

string

`US`

Workspace region: "US" or "EU"

`config.logLevel`

string

`debug`

Log level: "none", "error", "info", or "debug"

`config.autoTrackDeviceAttributes`

boolean

`true`

Automatically track device attributes

`config.trackApplicationLifecycleEvents`

boolean

`true`

Track app lifecycle events

`config.screenViewUse`

string

`all`

Screen view tracking: "all" (server + in-app) or "inapp" (in-app only)

`config.migrationSiteId`

string

Previously used site id, required if migrating from earlier versions (1.X)

`config.location`

object

Enable [location tracking](/integrations/sdk/expo/tracking/location/). Takes a `trackingMode` option: `MANUAL` (default), `ON_APP_START`, or `OFF`.

### Expo plugin configuration options[](#expo-plugin-configuration-options)

Option

Type

Default

Description

`android`

object

Required if you want to setup Android even if it is empty. Eg (`"android": {}`).

`android.googleServicesFile`

string

Set the path to your `google-services.json` file.

`android.setHighPriorityPushHandler`

boolean

This is optional, if you choose to use a 3rd party plugin to handle notification permissions, but want our SDK to handle the notifications.

`android.pushNotification.channel.id`

string

`[your package name]`

(Optional) Android messaging notification channel ID

`android.pushNotification.channel.name`

string

`[your app name] Notifications`

(Optional) Android messaging notification channel name

`android.pushNotification.channel.importance`

integer

`3`

(Optional) The importance of the channel. Acceptable values are 0 (min), 1 (low), 2 (medium), 3 (default/high), and 4 (urgent). See the [Android developer documentation](https://developer.android.com/develop/ui/views/notifications/channels#importance) for more about the behavior of each importance level

`ios`

object

**Required**: The parent object for iOS settings, including your initialization keys.

`ios.useFrameworks`

string

(Optional) Allows the plugin to work with static libraries. Options are `static` and `dynamic`

`ios.pushNotification`

object

**Required** Configurations for push notifications for iOS.

`ios.pushNotification.env`

object

**Required for manual initialization only** Contains your API key (`pushNotification.env.cdpApiKey`) and region (`pushNotification.env.region` with one of `eu` or `us`) information. Optional when using auto-initialization with `config` object.

`ios.pushNotification.env.cdpApiKey`

string

**Required**: the key to use for authentication

`ios.pushNotification.env.region`

`us` or `eu`

`us`

The region your Customer.io account is in. If you use the wrong region, your mobile traffic won’t make it into Customer.io.

`ios.pushNotification.autoFetchDeviceToken`

boolean

`true`

When `true`, the SDK automatically gets the device token. But, if you get tokens through another provider and register them with the SDK using the `CustomerIO.registerDeviceToken` method, you should set this to `false`. [Learn more](/integrations/sdk/expo/push-notifications/multiple-push-providers)

`ios.pushNotification.provider`

string

`apn`

Use `apn` for Apple Push Notification service or `fcm` for Firebase Cloud Messaging.

`ios.pushNotification.googleServicesFile`

string

**Required** if `ios.pushNotification.provider` is `fcm`. Set the path to your `GoogleService-Info.plist` file. **Do not set this value if you use the `@react-native-firebase` SDK.** [Learn more](#avoiding-conflicts-with-the-firebase-react-native-sdk)

`ios.pushNotification.useRichPush`

boolean

`false`

Enables rich push features for iOS (images and links).

`ios.pushNotification.disableNotificationRegistration`

boolean

`false`

(Optional) Removes the `registerPushNotification` handler and allows you to control notification permission requests. We recommend that you set this to `true` if you have `customerio-reactnative` version `>= 2.2.0` installed.

`ios.pushNotification.showPushAppInForeground`

boolean

`true`

Set to `true` if you want push notifications sent by Customer.io to be shown when your app is in the foreground.

`ios.pushNotification.handleDeeplinkInKilledState`

boolean

`false`

Set to `true` if you want the Customer.io SDK to handle deep links when your app is in a killed/closed state.

`ios.pushNotification.appGroupId`

string

(Optional) Your App Group identifier for [reliable push delivery tracking](/integrations/sdk/expo/push-notifications/app-groups/). Must start with `group.`. When set, the plugin automatically configures App Group entitlements and passes the identifier to the SDK. Requires `useRichPush: true`.

`location`

object

Container for [location tracking](/integrations/sdk/expo/tracking/location/) plugin settings.

`location.enabled`

boolean

`false`

When `true`, the plugin adds the native location dependencies (iOS Podfile subspec and Android Gradle properties) during prebuild. Your app must add its own location permissions and privacy usage descriptions.

## SDK packages[](#sdk-packages)

The SDK consists of a few packages. You *must* use the `CioConfig` and `CustomerIO` packages to configure and initialize the SDK in your React Native app.

Package Product

Required?

Description

CustomerIO

✅

The main SDK package. Used to initialize the SDK and call the SDK’s methods.

CioConfig

✅

Configure the SDK including in-app messaging support.

CioRegion

Used inside the `CioConfig.region` option to declare your region—EU or US.

CioLogLevel

Used inside the `CioConfig.logLevel` option to set the level of logs you can view from the SDK.

CioLocationTrackingMode

Used inside `CioConfig.location` to set the tracking mode. See [location tracking](/integrations/sdk/expo/tracking/location/) for details.

## React configuration options[](#react-configuration-options)

You can determine global behaviors for the SDK in using `CioConfig` package. You must provide configuration options before you initialize the SDK; you cannot declare configuration changes after you initialize the SDK.

Import `CioConfig` and then set configuration options to configure things like your logging level and whether or not you want to automatically track device attributes, etc. Note that the `logLevel` option requires the `CioLogLevel` package and the `region` option requires the `CioRegion` package.

```javascript
import {
  CioLogLevel,
  CioRegion,
  CustomerIO,
  CioConfig,
  PushClickBehaviorAndroid,
} from 'customerio-reactnative';

const App = () => {

useEffect(() => {
   const config: CioConfig = {
      cdpApiKey: 'CDP API Key', // Mandatory
      migrationSiteId: 'siteId', // Required if migrating from an earlier version
      region: CioRegion.US,
      logLevel: CioLogLevel.Debug,
      trackApplicationLifecycleEvents: true,
      inApp: {
         siteId: 'site_id',
      },
      push: {
         android: {
            pushClickBehavior: PushClickBehaviorAndroid.ActivityPreventRestart
         }
      }
   };
   CustomerIO.initialize(config)
   }, [])
}
```

Option

Type

Default

Description

`cdpApiKey`

string

**Required**: the key you'll use to initialize the SDK and send data to Customer.io

`region`

`CioRegion.EU` or `CioRegion.US`

`CioRegion.US`

****Requires the CioRegion package.** You must set the region your account is in the EU (`CioRegion.EU`).**

`apiHost`

string

The domain you’ll proxy requests through. You’ll only need to set this (and `cdnHost`) if you’re [proxying requests](#proxying-requests).

`autoTrackDeviceAttributes`

boolean

`true`

Automatically gathers information about devices, like operating system, device locale, model, app version, etc

`cdnHost`

string

The domain you’ll fetch configuration settings from. You’ll only need to set this (and `apiHost`) if you’re [proxying requests](#proxying-requests).

`logLevel`

string

`error`

**Requires the `CioLogLevel` package**. Sets the level of logs you can view from the SDK. Set to `debug` or `info` to see more logging output.

`migrationSiteId`

string

**Required if you're updating from 3.x**: the credential for previous versions of the SDK. This key lets the SDK send remaining tasks to Customer.io when your audience updates your app.

`screenViewUse`

`All` or `InApp`

`All`

`ScreenView.All` (Default): Screen events are sent to Customer.io. You can use these events to build segments, trigger campaigns, and target in-app messages.  
  
`ScreenView.InApp`: Screen view events not sent to Customer.io. You’ll *only* use them to target in-app messages based on page rules.

`trackApplicationLifecycleEvents`

boolean

`true`

Set to `false` if you don't want the app to send lifecycle events

`inApp`

object

**Required for in-app support**. This object takes a `siteId` property, determining the workspace your in-app messages come from.

`push`

object

Takes a single option called `PushClickBehaviorAndroid`. This object and option controls how your app behaves when your Android audience taps push notifications.

`location`

object

Enable [location tracking](/integrations/sdk/expo/tracking/location/). Takes a `trackingMode` option from the `CioLocationTrackingMode` package.

## Proxying requests[](#proxying-requests)

By default, requests go through our domain at `cdp.customer.io`. You can proxy requests through your own domain to provide a better privacy and security story, especially when submitting your app to app stores.

To proxy requests, you’ll need to set the `apiHost` and `cdnHost` properties in your `SDKConfigBuilder`. While these are separate settings, you should set them to the same URL.

While you need to initialize the SDK with a `cdpApiKey`, you can set this to any value you want. You only need to pass your actual key when you send requests from your server backend to Customer.io. If you want to secure requests to your proxy server, you can set the `cdpApiKey` to a value representing basic authentication credentials that you handle on your own. See [proxying requests](/integrations/data-in/proxying-requests) for more information.

```javascript
import { CioConfig, CustomerIO, CioRegion } from "customerio-reactnative";

useEffect(() => {
  const config: CioConfig = {
    cdpApiKey: process.env.EXPO_PUBLIC_CDP_API_KEY,
    region: CioRegion.US,
    apiHost: "proxy.example.com",
    cdnHost: "proxy.example.com", 
    inApp: {
      siteId: process.env.EXPO_PUBLIC_SITE_ID,
    }
  };
  CustomerIO.initialize(config);
}, []);
```

---

## Getting started > Troubleshooting

**Source:** /integrations/sdk/expo/getting-started/troubleshooting

# Troubleshooting

If you’re having trouble with the SDK, here are some basic steps to troubleshoot your problems, and solutions to some known issues.

## Basic troubleshooting steps[](#basic-troubleshooting-steps)

1.  **Make sure your app meets our [prerequisites](/integrations/sdk/expo/getting-started/#prerequisites)**: Attempting to use our SDK in an environment that doesn’t match our supported versions may result in build errors.

1.  **Update to the latest version**: When troubleshooting problems with our SDKs, we generally recommend that you try updating to the latest version. That helps us weed out issues that might have been seen in previous versions of the SDK.
2.  **Try running our MCP server**: Our MCP server includes an `integration` tool that can provide immediate help with your implementation, including problems with push and in-app notifications. See [Use our MCP server to troubleshoot your implementation](#troubleshoot-with-mcp) below.
3.  **Enable `debug` logging**: Reproducing your issue with `loglevel` set to `debug` can help you (or us) pinpoint problems.
    
     Don’t use debug mode in your production app
    
    Debug mode is great for helping you find problems as you integrate with Customer.io, but we strongly recommend that you set `loglevel` to `error` in your publicly available, production app.
    
4.  **Try our test image**: Using [an image](#image-display-issues) that we know works in push and in-app notifications can help you narrow down problems relating to images in your messages.

### If you need to contact support[](#if-you-need-to-contact-support)

We’re here to help! If you contact us for help with an SDK-related issue, we’ll generally ask for the following information. Having it ready for us can help us solve your problem faster.

1.  **Share information about your device and environment**: Let us know where you had an issue—the SDK and version of the SDK that you’re using, the specific device, operating system, message, use case, and so on. The more information you share with us, the easier it is for us to weed out externalities and find a solution.
    
2.  **Provide comprehensive debug logs**: When sharing logs with our support team, please ensure your logs include:
    
    *   **SDK initialization**: Show that the SDK was initialized with your site ID and API key
    *   **Profile identification**: Show that a profile was identified in your app
    *   **Issue reproduction**: Capture the exact issue you’re experiencing
    *   **Unfiltered logs**: Provide complete, unfiltered logs—don’t remove or filter out any log entries
    *   **Debug level enabled**: Make sure `loglevel` is set to `debug` when capturing logs for support
3.  **For push notification issues**:
    
    *   **Use live push examples**: If your issue relates to push notifications, provide logs from a **live push notification** sent through a campaign or API call, not a test send. Live pushes show the actual payload that was delivered to the profile.
    *   **Test in different app states**: Test and document the issue in various app states:
        *   **Foreground**: App is open and active
        *   **Background**: App is running but not in focus
        *   **Killed/Terminated**: App is completely closed
    *   **Include the push payload**: Share the complete push notification payload that you sent.
4.  **[Grant access to your workspace](/journeys/privacy/#support-team-access)**: It may help us to see exactly what triggers a campaign, what data is associated with devices you’re troubleshooting, etc. You can grant access for a limited time, and revoke access at any time.
    

### Troubleshooting issues with our MCP server[](#troubleshoot-with-mcp)

Our [MCP server](/ai/mcp-server/) includes an `integration` tool that can help troubleshoot your implementation, including problems with push and in-app notifications. It has a deep understanding of our SDKs and provides an immediate way to get support with your implementation—without necessarily needing to capture debug logs, etc.

You can ask the MCP server basic questions like, “My push notifications aren’t working. Can you help me troubleshoot the problem?”

Or you can ask more specific questions like, “Deep links in push notifications don’t work for customers in my Android app.” Or “I’m not receiving metrics for push notifications for iOS users.”

The tool will return detailed steps to help you find and troubleshoot problems.

## NaN, infinite, or imaginary number values[](#nan-infinite-or-imaginary-number-values)

Customer.io doesn’t handle invalid JSON values in your payloads, like `NaN`, infinite, or imaginary number values. If you send these values in `identify`, `track`, `screen`, or similar calls, we’ll drop them and record errors.

While we drop invalid values, we don’t drop the entire payload. The operation itself will still succeed. For example, if you send an identify call with two attributes, one of which is a `NaN` value, we’ll drop the `NaN` value, but the identify call succeeds with the other attribute.

## Push notification issues[](#push-notification-issues)

### Problems with rich push notifications (images, delivered metrics, etc)[](#problems-with-rich-push-notifications-images-delivered-metrics-etc)

If you have trouble with rich push features, like images not showing up in your push notifications, delivery metrics not being reported when a push notification is visible on the device, and so on, it’s possible that you either need to re-create your NSE target to support rich notifications your you may not have embeded the `NotificationServiceExtension` (NSE) at all.

1.  Remove your current NSE extension.
    
    1.  In XCode, select your project.
    2.  Go to the *Signing & Capabilities* tab.
    3.  Click the NotificationServiceExtension target; it has a bell icon next to it.
    4.  Click the minus sign to remove the target
    5.  Confirm the **Delete** operation.
        
        [![an image illustrating the 5 clicks you'll perform to delete your NSE target.](https://customer.io/images/delete-nse-1.png)](#c66794617622894d2a61b5a026240a3b-lightbox)
        
2.  Remove existing NSE files.
    
    1.  Right click the `NotificationServiceExtension` folder in your project and select **Delete**.
    2.  Confirm **Move to Trash**.
        
        [![an image illustrating the steps you'll take to delete NSE files.](https://customer.io/images/delete-nse-2.png)](#6a5a6ee211296a0c49beb046b941bfd8-lightbox)
        
3.  Recreate the notification service extension, following instructions for your framework. When You create your target NSE file, make sure you select your app’s name from the *Embed in Application* dropdown.
    
    [![xcode-servicenotification3.png](https://customer.io/images/xcode-servicenotification3.png)](#97f7eea0f5f268a29a24b1bdea3c767c-lightbox)
    
4.  Then add the required files:
    
    *   [React Native](/integrations/sdk/react-native/push-notifications/push/#integrate-push-capabilities-in-your-app)
    *   [Flutter](/integrations/sdk/flutter/push-notifications/push-setup/#swift-push)
    *   Expo (does this automatically)
    *   [iOS](/integrations/sdk/ios/push/#rich-push)
5.  After all files are added, go to the NSE target and, under the **General** tab, check **Deployment Target** and set it to a value that is identical to your host app’s iOS version.
    
    [![troubleshoot-nse.png](https://customer.io/images/troubleshoot-nse.png)](#7e7c735e2a16c72a6db71ef6cf9fc50b-lightbox)
    

When you create a new target, by default, XCode sets the highest version of deployment target version available. While testing if your device’s iOS version is lower than this deployment target, then the NSE won’t be connected to the main target and you won’t receive rich push notifications.

Then you can build and run your app to test if you can receive a rich push notification.

### Why aren’t devices added to people in Production builds?[](#why-arent-devices-added-to-people-in-production-builds)

If you see devices register successfully on your Staging builds, but not in Production or TestFlight builds, there might be an issue with your project setup. Check that the Push capability is enabled for both Release and Debug modes in your project. You might also need to enable the *Background Modes (Remote Notifications)* capability, depending on your project setup and messaging needs.

### Why didn’t everybody in my segment get a push notification?[](#why-didnt-everybody-in-my-segment-get-a-push-notification)

If your [segmentA group of people who match a series of conditions. People enter and exit the segment automatically when they match or stop matching conditions.](/journeys/data-driven-segments/) doesn’t specify people who have an existing device, it’s likely that people entered your segment without using your app. If you send a push notification to such a segment, the “Sent” count will probably show fewer sends than there were people in your segment.

### Why are messages sent but not delivered or opened?[](#why-are-messages-sent-but-not-delivered-or-opened)

The *sent* status means that we sent a message to your delivery provider—APNS or FCM. It’ll be marked *delivered* or *opened* when the delivery provider forwards the message to the device and the SDK reports the metric back to Customer.io. If a person turned their device off or put it in airplane mode, they won’t receive your push notification until they’re back on a network.

 Make sure you’ve configured your app to track metrics

If your app isn’t set up to capture push metrics, your app will *never* report `delivered` or `opened` metrics!

### Why don’t my messages play sounds?[](#why-dont-my-messages-play-sounds)

When you send a push notification to iOS devices that uses our SDK, you can opt to send the *Default* system sound or no sound at all. If your audience’s phone is set to vibrate, or they’ve disabled sound permissions for your app, the *Default* setting will cause the device to vibrate rather than playing a sound.

In most cases, you should use the *Default* sound setting to make sure your audience hears (or feels) your message. But, before you send sound, you should understand:

1.  Your app needs permission from your users to play sounds. This is done by your app, not our SDKs. [Here’s an example from our iOS sample app](https://github.com/customerio/customerio-ios/blob/main/Apps/APN-UIKit/APN%20UIKit/Util/NotificationUtil.swift#L12-L13) showing how to request sound permissions.
2.  iOS users can go into *System Settings* and disable sound permissions for your app. Enabling the *Default* setting doesn’t guarantee that your audience hears a sound when your message is delivered!

 We don’t support custom sounds yet

If you want to send a custom sound, you’ll need to handle it on your own, outside the SDK and use a custom payload when you set up your push notifications.

### FCM `SENDER_ID_MISMATCH` error[](#fcm-sender_id_mismatch-error)

This error occurs when the FCM Sender ID in your app does not match the Sender ID in your Firebase project. To resolve this issue, you’ll need to ensure that the Sender ID in your app matches the Sender ID in your Firebase project.

1.  Check that you uploaded the correct JSON certificate to Customer.io. If your JSON certificate represents the wrong Firebase project, you may see this error.
2.  Verify that the Sender ID in your app matches the Sender ID in your Firebase project.
3.  If you imported devices (device tokens) from a previous project, make sure that you imported tokens from the correct Firebase project. If the tokens represent a different app than the one you send push notifications to, you’ll see this error.

In some cases, we may make fixes in our iOS push packages that fix downstream issues in the Expo plugin. Before you contact support, you might want to \[update your iOS dependencies\](/Page(/integrations/sdk/expo/push/#update-ios-dependencies) to get the latest packages and see if that fixes the issue.

You can also check out our latest iOS changes to see if we’ve already fixed the issue or check out open issues to see if you’re experiencing a known issue.

### Deep links on iOS only open in a browser[](#deep-links-on-ios-only-open-in-a-browser)

It sounds like you want to use [universal links](/universal-links/#deep-links-vs-universal-links)—links that go to your app if a person has your app installed and to your website if they don’t. Universal links are a bit different than your average deep link and require a little bit of additional setup.

### Deep linking to iOS when your app is killed[](#troubleshoot-ios-deep-links)

There’s a known issue preventing deep links from working when your app is killed on iOS devices. When the app is in the killed state, the native click event is fired before the react app’s lifecycle starts. We recommend setting our Expo config `ios.pushNotification.handleDeeplinkInKilledState` to `true` to workaround the issue:

```json
{
  "plugins": [
    [
      "customerio-expo-plugin",
      {
        ...other config
        "ios": {
          ...other config
          "pushNotification": {
            ...other config
            "handleDeeplinkInKilledState": true,
            ...other config
          }
        }
      }
    ]
  ]
}
```

## Image display issues[](#image-display-issues)

If you’re having trouble, try using our test image in a message! If it works, then there’s likely a problem with your original image.

[![a test image of a bird that we know will work with all push notifications](https://customer.io/images/push-image-control.jpg)](#880a298d31bff7acc0ac56edbb6a6dc6-lightbox)

Android and iOS devices support different image sizes and formats. In general, you should stick to the smallest size (under 1 MB—the limit for Android devices) and common formats (PNG, JPEG).

iOS

Android

In-App (all platforms)

**Format**

JPEG, PNG, BMP, GIF

JPEG, PNG, BMP

JPEG, PNG, GIF

**Maximum size**

10 MB\*

1 MB

**Maximum resolution**

2048 x 1024 px

1038 x 1038 px

\*For linked media only. If you host images in our Asset Library, you’re limited to 3MB per image.

## In-App message issues[](#in-app-message-issues)

### My in-app messages are sent but not delivered[](#my-in-app-messages-are-sent-but-not-delivered)

People won’t get your message until they open your app. If you use page rules, they won’t see your message until they visit the right screen(s), so delivery times for in-app messages can vary significantly from other types of messages.

---

## In app messages > Handling and dismissing actions

**Source:** /integrations/sdk/expo/in-app-messages/handling-and-dismissing-actions

# In-app event listeners

## How it works[](#how-it-works)

In-app messages often have a call to action. Most basic actions are handled automatically by the SDK. For example, if you set a call-to-action button to open a web page, the SDK will open the web page when the user taps the button.

But you can also set up custom actions that require your app to handle the response. If you set up custom actions, you’ll need to handle the action yourself and dismiss the resulting message when you’re done with it.

### Handle responses to messages (event listeners)[](#event-listeners)

You can set up event listeners to handle your audience’s response to your messages. For example, you might run different code in your app when your audience taps a button in your message or when they dismiss the message without tapping a button.

You can listen for four different events:

*   `messageShown`: a message is “sent” and appears to a user
*   `messageDismissed`: the user closes the message (by tapping an element that uses the `close` action)
*   `errorWithMessage`: the message itself produces an error—this probably prevents the message from appearing to the user
*   `messageActionTaken`: the user performs an action in the message.

You can register an event listener after you initialize the SDK. In the code below, `event` is an instance of `InAppMessageEvent` containing details about the in-app message, e.g. `messageId`, `deliveryId`.

```javascript
import { CustomerIO, InAppMessageEventType } from "customerio-reactnative";

CustomerIO.inAppMessaging.registerEventsListener((event) => {
  switch (event.eventType) {
    case InAppMessageEventType.messageShown:
      // handle message shown
      break;
    case InAppMessageEventType.messageDismissed:
      // handle message dismissed
      break;
    case InAppMessageEventType.errorWithMessage:
      // handle message error
      break;
    case InAppMessageEventType.messageActionTaken:
      // event.actionValue => The type of action that triggered the event.
      // event.actionName => The name of the action specified when building the in-app message.
      // handle message action
      break;
  }
});
```

## Handling custom actions[](#handling-custom-actions)

When you set up an in-app message, you can determine the “action” to take when someone taps a button, taps your message, etc. In most cases, you’ll want to deep link to a screen, etc. But, in some cases, you might want to execute some custom action or code—like requesting that a user opts into push notifications or enables a particular setting.

In these cases, you’ll want to use the `messageActionTaken` event listener and listen for custom action names or values to execute code. While you’ll have to write custom code to handle custom actions, the SDK helps you [listen for in-app message events](#event-listeners) including your custom action, so you know when to execute your custom code.

1.  When you [add an action to an in-app message](/journeys/in-app-messages/) in Customer.io, select *Custom Action* and set your Action’s *Name* and value. The *Name* corresponds to the `actionName`, and the value represents the `actionValue` in your event listener.
    
    [![Set up a custom in-app action](https://customer.io/images/in-app-custom-action.png)](#1005ee0b179f3999e81398a57e369557-lightbox)
    
2.  [Register an event listener](#event-listener) for `MessageActionTaken`, and listen for the `actionName` or `actionValue` you set up in the previous step.
    
     Use names and values exactly as entered
    
    We don’t modify your action’s name or value, so you’ll need to match the case of names or values exactly as entered in your *Custom Action*.
    
3.  When someone receives a message and invokes the action (tapping a button, tapping a message, etc), your app will perform the custom action.

## Dismiss in-app message[](#dismiss-in-app-message)

You can dismiss the currently display in-app message with the following method. This can be particularly useful to dismiss in-app messages when your audience clicks or taps custom actions.

```javascript
CustomerIO.inAppMessaging.dismissMessage();
```

---

## In app messages > In app

**Source:** /integrations/sdk/expo/in-app-messages/in-app

# Set up in-app messages

## How it works[](#how-it-works)

Unlike push notifications, where you need to add settings to your Expo `plugins`, in-app messaging is enabled automatically. You simply need to add an `inApp` object with your `site_id` to your `CioConfig` to support in-app messages.

An in-app message is a message that people see in your app. If you set an *expiry* period for your message, and that time elapses before someone opens your app, they won’t see your message.

You can also set *page rules* to display your in-app messages when people visit specific screens in your app. However, to take advantage of page rules, you need to use [screen tracking](/integrations/sdk/expo/tracking/screen/#auto-screenview) features. Screen tracking tells us the names of your screens and which screen a user is on, so the SDK displays in-app messages on the correct screens in your app.

## Set up in-app messaging[](#set-up-in-app-messaging)

When you set up in-app messaging, you’ll need your workspace’s site ID.

1.  If you haven’t already, enable in-app messaging under [**Settings** > **Workspace Settings** > **In App Settings**](https://fly.customer.io/env/last/settings).
    
2.  Add the `inApp` configuration option with your `site_id` where you initialize the Customer.io SDK in your React Native app. To find your Site ID:
    
    1.  Go to and select **Workspace Settings** in the upper-right corner of the Customer.io app and go to [**API and Webhook Credentials**](https://fly.customer.io/workspaces/last/settings/api_credentials).
        
    2.  Copy the **Site ID** for the set of credentials that you want to send your in-app messages from. If you don’t have a set of credentials, click **Create Tracking API Key** to generate them.
        
        [![find your site ID](https://customer.io/images/cdp-js-site-id.png)](#64d2b27827ffddb00dc77b851a7a6854-lightbox)
        
    
    ```javascript
     import {
         CioRegion, CustomerIO, CioConfig
     } from 'customerio-reactnative';
    
     const App = () => {
    
     useEffect(() => {
         const config: CioConfig = {
             cdpApiKey: 'CDP API Key', // Mandatory
             migrationSiteId: 'siteId', // Required if migrating from an earlier version
             region: CioRegion.US,
             inApp: {
                 siteId: 'site_id',
             }
         };
         CustomerIO.initialize(config)
         }, [])
     }
    ```
    

## Page rules[](#page-rules)

You can set page rules when you create an in-app message. A page rule determines the page that your audience must visit in your app to see your message. However, before you can take advantage of page rules, you need to:

1.  Track screens in your app. See the [Track Events](/integrations/sdk/react-native/tracking/track-events/#manual-screenview) page for help sending `screen` events.
2.  Provide page names to whomever sets up in-app messages in fly.customer.io. If we don’t recognize the page that you set for a page rule, your audience will never see your message.

[![Set up page rules to limit in app messages by page](https://customer.io/images/in-app-page-rule.png)](#a55af0f9917c15a7b484c9df200f448d-lightbox)

Keep in mind: page rules are case sensitive. Make sure your page rules match the casing of the `title` in your `screen` events.

[![The first page rule is Web contains /dashboard. The second page rule is iOS contains Dashboard.](https://customer.io/images/page-rule-case-sensitive.png)](#ba51bbdc9b4c25b5402f99a8a9d30245-lightbox)

## Anonymous messages[](#anonymous-messages)

As of version 2.8, you can send [anonymous in-app messages](/journeys/anonymous-in-app/). These are messages that are sent *only* to people you haven’t identified yet.

You *can* use lead forms in anonymous messages to capture leads and potentially identify people when they submit your form. For example, you could use a lead form and offer a coupon or newsletter to people who provide their email addresses. See [Lead forms](/journeys/messages-and-webhooks/in-app/lead-form/) for more information.

---

## In app messages > Inbox

**Source:** /integrations/sdk/expo/in-app-messages/inbox

# Notification inbox

Requires v3.1+This feature requires SDK version 3.1 or later. 

 Minimum SDK required: 3.1

Users who don’t have a version of your app with Customer.io SDK version 3.1 or later will not see this feature and related messages. If you send inbox messages to users without the minimum required version, they won’t be *delivered*.

## How it works[](#how-it-works)

Unlike other messages, inbox messages don’t necessarily appear immediately to users, and they don’t disappear when the user dismisses them. Instead, you’ll display these messages through a notification inbox that your audience can access at their leisure.

Customer.io delivers inbox messages as JSON payloads, not fully-rendered messages. The SDK helps you listen for these payloads, but you’ll determine how to display them in your own inbox client.

You can send an inbox message as a part of a [campaign, broadcast](/journeys/send-inbox/), or [transactional message](/journeys/send-inbox-txnl/).

## Get the inbox instance[](#get-the-inbox-instance)

You’ll access inbox functionality through the `inbox()` method on the in-app messaging module.

```javascript
const inbox = CustomerIO.inAppMessaging.inbox();
```

## Inbox methods[](#inbox-methods)

The inbox instance provides several methods to manage messages.

Method

Description

`getMessages()`

Fetch all messages from the inbox. Returns a Promise with the list of messages.

`getMessages(topic)`

Fetch messages filtered by topic. Returns a Promise with the filtered list of messages.

`subscribeToMessages(callbacks)`

Subscribe to inbox updates for all messages. Returns a subscription object.

`subscribeToMessages(callbacks, topic)`

Subscribe to inbox updates filtered by topic. Returns a subscription object.

`markMessageOpened(message)`

Mark a message as opened.

`markMessageUnopened(message)`

Mark a message as unopened.

`markMessageDeleted(message)`

Mark a message as deleted.

`trackMessageClicked(message)`

Track a click on the message without an action name.

`trackMessageClicked(message, actionName)`

Track a click on the message with an action name.

## Inbox message payloads[](#inbox-message-payloads)

Inbox messages are delivered as a JSON payload. The SDK helps you listen for the payload, but you’ll render the content in your own inbox client.

The client payload includes the following fields, but you’re most concerned with the `properties` object, which represents your message content. By default, we’ll send a `title` and `body` field, but you can add other fields like an `image` or a `link`—whatever you set up your inbox to expect.

Make sure that your team members know what payloads to send—especially if you expect different payloads for different topics or types of messages.

Field

Type

Description

`messageId`

string

Unique identifier for the message.

`sentAt`

string

When the message was sent.

`expiresAt`

string

When the message will expire.

`opened`

boolean

Whether the message has been opened.

`topics`

array

The topics that the message belongs to.

`type`

string

The type of message.

`properties`

object

The properties of the message.

```json
{
    "messageId": "1234567890",
    "sentAt": "2026-02-05T12:00:00Z",
    "expiresAt": "2026-02-05T12:00:00Z",
    "opened": false,
    "topics": ["orders", "shipping"],
    "type": "order_shipped",
    "properties": {
        "title": "Hey Cool Person, your order shipped!",
        "body": "You can track your order #1234567890 here:",
        "link": "https://example.com/orders/1234567890"
    }
}
```

### Inbox topics and types[](#inbox-topics-and-types)

When you send an inbox message, you can assign it to one or more topics. You can use these topics to filter messages when you fetch them. You can also use the topics to determine how to render the messages in your notification inbox.

Messages also have a `type`. Think of this like a sub-category or topic for a message. For example, you might have `orders` and `sale` topics, where `orders` don’t have images but `sale` topics might. Or, within the `orders` topic, you might have `order_placed` and `order_shipped` types, where `order_placed` lists order details and images of purchased products and `order_shipped` provides a link to the tracking information for the order that opens in a new tab.

## Setup your notification inbox[](#setup-your-notification-inbox)

Inbox messages are just JSON payloads. You’ll need to build your own inbox client to display the messages. The code below gives you a starting point, but you can build your own inbox client however you want.

### Fetch messages[](#fetch-messages)

```javascript
// Fetch all messages
const messages = await inbox.getMessages();

// Fetch messages filtered by topic
const promotions = await inbox.getMessages('promotions');
```

### Subscribe to inbox updates[](#subscribe-to-inbox-updates)

```javascript
// Subscribe to all messages
const subscription = inbox.subscribeToMessages({
  onMessagesChanged: (messages) => {
    console.log('Messages updated:', messages);
    // Update your UI with the messages
    setMessages(messages);
  }
});

// Subscribe to messages filtered by topic
const topicSubscription = inbox.subscribeToMessages({
  onMessagesChanged: (messages) => {
    console.log('Topic messages:', messages);
    // Update your UI with filtered messages
    setTopicMessages(messages);
  }
}, 'announcements');

// Don't forget to unsubscribe when you're done
subscription.remove();
topicSubscription.remove();
```

### Mark messages as opened or unopened[](#mark-messages-as-opened-or-unopened)

```javascript
// Mark a message as opened
inbox.markMessageOpened(message);

// Mark a message as unopened
inbox.markMessageUnopened(message);
```

### Track message clicks[](#track-message-clicks)

```javascript
// Track a click without an action name
inbox.trackMessageClicked(message);

// Track a click with an action name
inbox.trackMessageClicked(message, 'view_offer');
```

### Delete messages[](#delete-messages)

```javascript
// Mark a message as deleted
inbox.markMessageDeleted(message);
```

## Working with message properties[](#working-with-message-properties)

You can access message properties to display custom content in your inbox:

```javascript
// Access message properties
const { title, body, link, image } = message.properties;

// Handle message action when user taps
const handleMessagePress = (message) => {
  // Mark as opened
  inbox.markMessageOpened(message);

  // Track click
  inbox.trackMessageClicked(message);

  // Open link if available
  if (message.properties.link) {
    Linking.openURL(message.properties.link);
  }
};

// Track with specific action name
const handleActionButton = (message, actionType) => {
  inbox.markMessageOpened(message);
  inbox.trackMessageClicked(message, actionType);

  // Navigate based on action
  switch (actionType) {
    case 'view_offer':
      navigation.navigate('OfferDetails', { offerId: message.properties.offerId });
      break;
    case 'view_order':
      navigation.navigate('OrderDetails', { orderId: message.properties.orderId });
      break;
  }
};
```

---

## In app messages > Inline in app

**Source:** /integrations/sdk/expo/in-app-messages/inline-in-app

# Inline in-app messages

Inline in-app messages help you send dynamic content into your app. The messages can look and feel like a part of your app, but provide fresh and timely content without requiring app updates.

## How it works[](#how-it-works)

An inline message targets a specific view in your app. Basically, you’ll create an empty placeholder view in your app’s UI, and we’ll fill it with the content of your message.

This makes it easy to show dynamic content in your app without development effort. You don’t need to force an update every time you want to talk to your audience. *And*, unlike push notifications, banners, toasts, and so on, in-line messages can look like natural parts of your app.

## 1\. Add View to your app UI to support inline messages[](#1-add-view-to-your-app-ui-to-support-inline-messages)

You’ll need to include a UI element in your app UI to render inline messages. The view will automatically adjust its height when messages are loaded or interacted with.

 We’ve set up examples in [our test app](https://github.com/customerio/customerio-expo-plugin/tree/main/test-app) that might help if you want to see a real-world implementation of this feature.

Add the `InlineInAppMessageView` component to your Expo app:

```jsx
import { InlineInAppMessageView } from 'customerio-reactnative';

function MyComponent() {
  return (
    <InlineInAppMessageView
      elementId="my-message"
      onActionClick={(message, actionValue, actionName) => {
        console.log('Action clicked:', { message, actionValue, actionName });
      }}
    />
  );
}
```

### View layout[](#view-layout)

The `InlineInAppMessageView` automatically adjusts its height at runtime when messages load or users interact with them. You should avoid setting a fixed height on this component as it might interfere with message rendering.

You’re responsible for setting layout styles to position your view correctly (width, margins, padding, and so on). The component will handle its own height dynamically.

## 2\. Build and send your message[](#2-build-and-send-your-message)

When you add an in-app message to a broadcast or campaign in Customer.io:

1.  Set the **Display** to **Inline** and set the **Element ID** to the ID you set in your app. If the editor says that the inline display feature is *Web/iOS only*, don’t worry about that. We’re working on updating this UI.
2.  (Optional) If you send multiple messages to the same Element ID, you’ll also want to set the **Priority**. This determines which message we’ll show to your audience first, if there are multiple messages in the queue.

Then craft and send your message!

[![in-app message settings with the To field set to ID and the display setting set to inline](https://customer.io/images/in-app-inline.png)](#74810b23329cd479a11edfdda9f818e0-lightbox)

## Handling custom actions[](#handling-custom-actions)

When you set up an in-app message, you can determine the “action” to take when someone taps a button, taps your message, etc. In most cases, you’ll want to deep link to a screen, etc. But, in some cases, you might want to execute some custom action or code—like requesting that a user opts into push notifications or enables a particular setting.

While you’ll have to write custom code to handle custom actions, the SDK helps you listen for in-app message events including your custom action, so you know when to execute your custom code.

Follow the steps below to implement custom actions for inline messages:

### 1\. Compose an in-app message with a custom action[](#1-compose-an-in-app-message-with-a-custom-action)

When you [add an action to an in-app message](/journeys/in-app-messages/) in Customer.io, select *Custom Action* and set your Action’s *Name* and value. The *Name* corresponds to the `actionName`, and the value represents the `actionValue` in your event listener.

[![Set up a custom in-app action](https://customer.io/images/in-app-custom-action.png)](#1005ee0b179f3999e81398a57e369557-lightbox)

### 2\. Listen for events[](#2-listen-for-events)

There are two ways to listen for these click events in inline in-app messages.

1.  Register a callback with your inline view:
    
    ```jsx
    import { InlineInAppMessageView } from 'customerio-reactnative';
    
    function MyComponent() {
      const handleActionClick = (message, actionValue, actionName) => {
        // Perform some logic when people tap an action button.
    
        // Example code handling button tap:
        switch (actionValue) { // use actionValue or actionName, depending on how you composed the in-app message.
          case "enable-auto-renew":
            // Perform the action to enable auto-renew
            enableAutoRenew(actionName);
            break;
    
          // You can add more cases here for other actions
          default:
            // Handle unknown actions or do nothing
            console.log("Unknown action:", actionValue);
        }
      };
    
      return (
        <InlineInAppMessageView
          elementId="my-message"
          onActionClick={handleActionClick}
        />
      );
    }
    ```
    
2.  Register a global SDK event listener.
    
    When you register an [event listener](../in-app-event-listeners/#event-listeners) with the SDK, we’ll call the `messageActionTaken` event listener. We call this event listener for both modal and inline in-app message types, so you can reuse logic for inline and non-inline messages if you want.
    

## Handle responses to messages (event listeners)[](#event-listeners)

Like [modal in-app messages](../in-app-event-listeners/#event-listeners), you can set up event listeners to handle your audience’s response to your messages.

For inline messages, you can listen for three different events:

*   `messageShown`: a message is “sent” and appears to a user.
*   `errorWithMessage`: the message itself produces an error—this probably prevents the message from appearing to the user.
*   `messageActionTaken`: the user performs an action in the message. As [shown above](#2-listen-for-events), this is only called if the View instance doesn’t have an `onActionClick` callback set.

Unlike modal in-app messages, you’ll notice that there’s no `messageDismissed` event. This is because inline messages don’t really have a concept of dismissal like modal messages do. They’re meant to be a part of your app!

---

## Push notifications > App groups

**Source:** /integrations/sdk/expo/push-notifications/app-groups

# App Groups for push tracking

App Groups are required for reliable push delivery tracking on iOS. Without this setup, delivery metrics may be lost if iOS terminates the Notification Service Extension before the tracking request completes. With App Groups, the SDK automatically recovers these lost metrics on the next app launch.

## Before you begin[](#before-you-begin)

Before you configure App Groups, make sure you’ve completed the following:

*   [Set up push notifications](/integrations/sdk/expo/push-notifications/push/) with `useRichPush: true` enabled
*   Created an App Group identifier in the [Apple Developer Portal](https://developer.apple.com/). The identifier must start with `group.`—for example, `group.com.example.myapp.cio`.

## Add the App Group ID to your plugin configuration[](#add-the-app-group-id-to-your-plugin-configuration)

To enable App Groups, add `appGroupId` to the `ios.pushNotification` section of your `customerio-expo-plugin` configuration. The Expo plugin handles the rest—it automatically:

*   Adds the App Group capability to both your main app target and the Notification Service Extension
*   Configures the entitlements for both targets
*   Passes the App Group identifier to the SDK initialization

 Auto-initialization (Expo 53+)

#### Auto-initialization (Expo 53+)[](#Auto-initialization \(Expo 53+\))

```json
{
   "expo": {
      "plugins": [
         [
            "customerio-expo-plugin",
            {
               "config": {
                  "cdpApiKey": "<CDP_API_KEY>",
                  "region": "us"
               },
               "ios": {
                  "pushNotification": {
                     "useRichPush": true,
                     "appGroupId": "group.com.example.myapp.cio"
                  }
               }
            }
         ]
      ]
   }
}
```

 Manual initialization

#### Manual initialization[](#Manual initialization)

```json
{
   "expo": {
      "plugins": [
         [
            "customerio-expo-plugin",
            {
               "ios": {
                  "pushNotification": {
                     "useRichPush": true,
                     "appGroupId": "group.com.example.myapp.cio",
                     "env": {
                        "cdpApiKey": "<CDP_API_KEY>",
                        "region": "us"
                     }
                  }
               }
            }
         ]
      ]
   }
}
```

After updating your configuration, rebuild your app:

```bash
npx expo prebuild --clean
```

 appGroupId requires useRichPush

App Groups rely on the Notification Service Extension to track delivery metrics. You must set `useRichPush: true` for `appGroupId` to take effect.

---

## Push notifications > Deep links

**Source:** /integrations/sdk/expo/push-notifications/deep-links

# Deep Links

## How it works[](#how-it-works)

Deep links are the links that directs users to a specific location within a mobile app. When you set up your notification, you can set a “deep link.” When your audience taps the notification, the SDK will route users to the right place.

Deep links help make your message meaningful, with a call to action that makes it easier, and more likely, for your audience to follow. For example, if you send a push notification about a sale, you can send a deep link that takes your audience directly to the sale page in your app.

However, to make deep links work, you’ll have to handle them in your app. We’ve provided instructions below to handle deep links in both Android and iOS versions of your app.

## Handle deep links in your app[](#handle-deep-links-in-your-app)

There are a number of ways to enable deep links. Our example below uses `@react-navigation` with a `config` and `prefix` to automatically set paths. The paths are the values you’d use in your push payload to send a link.

```javascript
import { NavigationContainer } from '@react-navigation/native';
const config = {
 screens: {
   Home: {
     path: 'home/:id?',
     parse: {
       id: (id: String) => `${id}`,
     },
   },
 }
};
const linking = {
    prefixes: ['myapp://'],
    config
};
```

## Set up deep links for Android[](#set-up-deep-links-for-android)

To set up deep links in Android, you need to declare your `intentFilters` in your `app.json` or `app.config.js`.

```json
{
    "expo": {
      ...
      "android": {
        ...
        "intentFilters": [
          {
            "action": "VIEW",
            "data": [
              {
                "scheme": "<YourScheme>",
                "host": "<YourHost>"
              }
            ],
            "category": [
              "BROWSABLE",
              "DEFAULT"
            ]
          }
        ]
      }
      ...
    }
}
```

### Push Click Behavior[](#push-click-behavior)

The `android.pushClickBehavior` config option controls how your app behaves when your audience taps push notifications. The SDK automatically tracks `Opened` metrics for all options.

```json
{
   "plugins": [
      [
         "customerio-expo-plugin",
         {
            "android": {
               "pushClickBehavior": "ActivityPreventRestart"
            }
         }
      ]
   ]
}
```

The available options are:

*   `ACTIVITY_PREVENT_RESTART` **(Default)**: If your app is already in the foreground, the SDK will not re-create your app when your audience clicks a push notification. Instead, the SDK will reuse the existing activity. If your app *is not* in the foreground, we’ll launch a new instance of your deep-linked activity. We recommend that you use this setting if your app has screens that your audience shouldn’t navigate away from—like a shopping cart screen.
    
*   `ACTIVITY_NO_FLAGS`: If your app is in the foreground, the SDK will re-create your app when your audience clicks a notification. The activity is added on top of the app’s existing navigation stack, so if your audience tries to go back, they will go back to where they previously were.
    
*   `RESET_TASK_STACK`: No matter what state your app is in (foreground, background, killed), the SDK will re-create your app when your audience clicks a push notification. Whether your app is in the foreground or background, the state of your app will be killed so your audience cannot go back to the previous screen if they press the back button.
    

## Set up deep links for iOS[](#set-up-deep-links-for-ios)

To support iOS, you need to declare your deep link scheme in your Expo project’s `app.json` or `app.config.js` file.

Apple has a few different ways to set up deep links. [Learn more about URL schemes for iOS apps](https://developer.apple.com/documentation/xcode/defining-a-custom-url-scheme-for-your-app).

```json
{
   "expo": {
      ...
      "ios": {
      ...
         "infoPlist": {
            "CFBundleURLTypes": [
               {
               "CFBundleURLSchemes": ["<YourScheme>"]
               }
            ]
         }
      }
      ...
   }
}
```

 There’s an issue deep linking into iOS when the app is closed

In iOS, deep link click events won’t fire when your app is closed. See our [troubleshooting section](/integrations/sdk/expo/troubleshooting/#troubleshoot-ios-deep-links) for a workaround to this issue.

---

## Push notifications > Multiple push providers

**Source:** /integrations/sdk/expo/push-notifications/multiple-push-providers

# Multiple push providers

By default, our Expo plugin expects to be the only push package in your project. It’ll automatically handle push notification metrics and fetch a device tokens for you. But if you use our SDK with other push packages like `react-native-firebase`, you may want to make a couple of configuration changes.

## Use React Native Firebase with the Customer.io SDK[](#use-react-native-firebase-with-the-customerio-sdk)

If you use `react-native-firebase` with the Customer.io SDK, you’ll need to make sure that your `app.json` or `app.config.js` file doesn’t include the `ios.pushNotification.googleServicesFile` option.

This ensures that you properly capture metrics for push notifications sent through Customer.io and any notifications you receive using `react-native-firebase`.

```json
{
   "expo": {
        "ios": {
            // include plist file here
            "googleServicesFile": "./GoogleService-Info.plist"
        },
        "plugins": [
            [
                "customerio-expo-plugin",
                {
                    "android": {
                        "googleServicesFile": "./files/google-services.json"
                    },
                    "ios": {
                        "pushNotification": {
                            "provider": "fcm", 
                            // don't include your plist file here
                        }
                    }
                }
            ]
        ]
    }
}
```

## Capture push tokens from another push provider[](#capture-push-tokens-from-another-push-provider)

If you use another push package like `expo-notifications`, and you want to utilize that package to get device tokens, you’ll need to update your `app.json` or `app.config.js` file to disable automatic device token fetching and make sure you register push tokens with Customer.io using the `CustomerIO.registerDeviceToken(<token>)` method.

1.  In your `app.json` or `app.config.js` file, set the `ios.pushNotification.autoFetchDeviceToken` option to `false`.
    
    ```json
    {
       "plugins": [
          [
             "customerio-expo-plugin",
             {
                "ios": {
                   "pushNotification": {
                      "autoFetchDeviceToken": false
                   }
                }
             }
          ]
       ]
    }
    ```
    
2.  Capture push tokens from the other provider and register them with our SDK using the `CustomerIO.registerDeviceToken(<token>)` method.

---

## Push notifications > Push

**Source:** /integrations/sdk/expo/push-notifications/push

# Set up push notifications

The Expo plugin supports push notifications over APN for iOS and FCM for Android. Use this page to get started with push notification services.

## How it works[](#how-it-works)

When you use our Expo plugin, you’re already set up to support push notifications, including images and deep links.

Before a device can receive a push notification, you must:

1.  [Identify a person](/integrations/sdk/expo/tracking/identify/). This associates a token with the person; you can’t send push notifications to a device until you identify the recipient.
2.  (Optional) Set up your app to report push metrics back to Customer.io.

## Before you begin[](#before-you-begin)

You need to enable Customer.io to send push notifications through your preferred service: [Apple Push Notification Service (APNs)](/journeys/push-getting-started/#for-ios) and/or [Firebase Cloud Messaging (FCM)](/journeys/push-getting-started/#for-android) before you can send push notifications through Customer.io.

 We don’t support Expo’s Push Service

You can’t use push tokens obtained through [Expo’s Push Service](https://docs.expo.dev/push-notifications/sending-notifications/) to send push notifications through Customer.io. If your app previously used Expo’s push service, you’ll have to obtain new FCM or APN tokens to send push notifications through Customer.io.

Luckily, you don’t need to do anything to support your app’s users when you implement our SDK! If your app can obtain Expo push tokens, it should also be set up to receive APN or FCM device tokens. You’ll simply configure your app to [fetch a token](#fetch-the-current-device-token) and send that token to Customer.io using `CustomerIO.registerDeviceToken("<apn or fcm token here>")`.

## Set up push for Android[](#android-push)

You don’t need to do anything to support Android devices. When you use our Expo plugin, your Android audience is automatically set up to receive push notifications.

## Set up push on iOS[](#register-push)

For the most part, we’ll set up push notifications for you when you run `expo prebuild`. But before you can support push notifications for iOS devices, you need to add Push Notification capabilities to your project in XCode.

1.  Open your React Native project in XCode, go to the `ios` subfolder and open `<yourAppName>.xcworkspace`.
2.  Select your project, and then under *Targets*, select your main app.
3.  Click the **Signing & Capabilities** tab
4.  Click **Capability**.
5.  Add **Push Notifications** to your app.
    
    [![add push notification capabilities to your app](https://customer.io/images/react-native-xcode-push.png)](#b837646bba75943a4f08d0fee059210c-lightbox)
    

When you’re done, you’ll see **Push Notifications** added to your app’s capabilities. Now you’re ready to run the `expo prebuild`.

[![this is what it looks like when you've successfully added push notifications to your app](https://customer.io/images/react-native-xcode-push-added.png)](#8a72141293cedd80f2bd0345069d4f43-lightbox)

### Update iOS dependencies[](#update-ios-dependencies)

In some cases, we may make fixes in our iOS push packages that fix downstream issues in the Expo plugin. The command to update these packages depends on whether your Expo project uses a managed or bare workflow. For managed workflows, you can simply re-run the prebuild.

 Managed workflow

#### Managed workflow[](#Managed workflow)

```shell
expo prebuild --clean
# the --clean option is optional
```

 Bare workflow

#### Bare workflow[](#Bare workflow)

```shell
pod update --repo-update --project-directory=ios
```

### EAS Build with Rich Push[](#eas-build-rich-push)

If you use [EAS Build](https://docs.expo.dev/build/introduction/) with `useRichPush: true`, the Customer.io plugin automatically creates a notification service extension during `expo prebuild`. You need to tell EAS Build about this extension by adding the `appExtensions` configuration to your `app.config.js`.

 The `appExtensions` configuration uses an [experimental Expo feature](https://docs.expo.dev/build-reference/app-extensions/). The configuration structure may change in future Expo versions.

```javascript
export default {
  expo: {
    ios: {
      bundleIdentifier: "com.yourcompany.yourapp"
    },
    extra: {
      eas: {
        build: {
          experimental: {
            ios: {
              appExtensions: [
                {
                  targetName: "NotificationService",
                  bundleIdentifier: "com.yourcompany.yourapp" + ".richpush",
                  entitlements: {
                    "com.apple.developer.usernotifications.service": true
                  }
                }
              ]
            }
          }
        }
      }
    }
  }
}
```

**Finding the extension bundle identifier**: After running `expo prebuild`, you can find the exact bundle identifier in your `ios/<YourAppName>.xcworkspace` project. Open the project in Xcode, look for the `NotificationService` target, and check its bundle identifier in the project settings. It should be your main app’s bundle identifier with `.richpush` appended (e.g., `com.yourcompany.yourapp.richpush`).

### Sound in push iOS push notifications[](#sound-in-push-notifications)

When you send a push notification to iOS devices that uses our SDK, you can opt to send the *Default* system sound or no sound at all. If your audience’s phone is set to vibrate, or they’ve disabled sound permissions for your app, the *Default* setting will cause the device to vibrate rather than playing a sound.

In most cases, you should use the *Default* sound setting to make sure your audience hears (or feels) your message. But, before you send sound, you should understand:

1.  Your app needs permission from your users to play sounds. This is done by your app, not our SDKs. [Here’s an example from our iOS sample app](https://github.com/customerio/customerio-ios/blob/main/Apps/APN-UIKit/APN%20UIKit/Util/NotificationUtil.swift#L12-L13) showing how to request sound permissions.
2.  iOS users can go into *System Settings* and disable sound permissions for your app. Enabling the *Default* setting doesn’t guarantee that your audience hears a sound when your message is delivered!

 We don’t support custom sounds yet

If you want to send a custom sound, you’ll need to handle it on your own, outside the SDK and use a custom payload when you set up your push notifications.

## Prompt users to opt-into push notifications[](#prompt-users-to-opt-into-push-notifications)

Your audience has to opt into push notifications. To display native iOS and Android push notification permission prompts, you’ll use the `CustomerIO.pushMessaging.showPromptForPushNotifications` method.

You can configure push notifications to request authorization for sounds and badges as well (only on iOS). If a user opts into push notifications, the `CustomerIO.pushMessaging.showPromptForPushNotifications` method will return `Granted`, otherwise it returns `Denied` as a `string`. If the user already responded to the push authorization prompt, the current authorization status is returned as a string.

```javascript
var options = {"ios" : {"sound" : true, "badge" : true}}
CustomerIO.showPromptForPushNotifications(options).then(status => {
  switch(status) {
    case "Granted":
     // Push permission is granted, your app can now receive push notifications
      break;
    case "Denied":
      // App is not authorized to receive push notifications
      // You might need to explain users why your app needs permission to receive push notifications
      break;
    case "NotDetermined":
      // Push permission status is not determined (Only for iOS)
      break;
  }
}).catch(error => {
  // Failed to show push permission prompt
  console.log(error)
})
```

### Get a user’s permission status[](#get-a-users-permission-status)

To get a user’s current permission status, call the `CustomerIO.getPushPermissionStatus()` method. This returns a promise with the current status as a string.

```javascript
CustomerIO.getPushPermissionStatus().then(status => {
  console.log("Push permission status is - " + status)
})
```

### Fetch the current device token[](#fetch-the-current-device-token)

You can fetch the currently stored device token using the `CustomerIO.pushMessaging.getRegisteredDeviceToken()` method. This method returns an APN/FCM token in a promise as a string.

```javascript
   let token = await CustomerIO.pushMessaging.getRegisteredDeviceToken()
   if (token) {
    // Use the token as required in your app for example save in a state
        setDeviceToken(token);
   }
```

## Test your implementation[](#test-rich-push)

After you set up rich push, you should test your implementation. Below, we show the payload structure we use for iOS and Android. In general, you can use our regular rich push editor; it’s set up to send messages using the JSON structure we outline below.

If you want to fashion your own payload, you can use our [custom payload](/journeys/push-custom-payloads/#getting-started-with-custom-payloads).

[![the rich push editor](https://customer.io/images/push-preview.png)](#4e089ac68a22d5b994db09266a531737-lightbox)

  

 iOS APNs payload

#### iOS APNs payload[](#iOS APNs payload)

```json
{
    "aps": {
        // basic iOS message and options go here
        "mutable-content": 1,
        "alert": {
            "title": "string", //(optional) The title of the notification.
            "body": "string" //(optional) The message you want to send.
        }
    },
    "CIO": {
        "push": {
            "link": "string", //generally a deep link, i.e. my-app:://...
            "image": "string" //HTTPS URL of your image, including file extension
        }
    }
}
```

*   CIO object
    
    Contains options supported by the Customer.io SDK.
    
    *   push object
        
        Required Describes push notification options supported by the CIO SDK.
        

 Android payload

#### Android payload[](#Android payload)

```json
{
  "message": {
    "data": {
      "title": "string", //(optional) The title of the notification.
      "body": "string", //The message you want to send.
      "image": "string", //https URL to an image you want to include in the notification
      "link": "string" //Deep link in the format remote-habits://deep?message=hello&message2=world
    }
  }
}
```

*   message 
    
    Required The parent object for all push payloads.
    
    *   android object
        
        Contains properties that are **not** interpreted by the SDK but are defined by FCM. You need to write your own code to handle these Android push features.
        
    *   data object
        
        Required Contains all properties interpreted by the SDK.
        
    
    *   android object
        
        Contains properties that are **not** interpreted by the SDK but are defined by FCM. You need to write your own code to handle these Android push features.
        
    *   data object
        
        Contains the `link` property (interpreted by the SDK) and additional properties that you want to pass to your app.
        
    *   notification object
        
        Required Contains properties interpreted by the SDK except for the `link`.

---

## Push notifications > Push metrics

**Source:** /integrations/sdk/expo/push-notifications/push-metrics

# Capture Push Metrics

## How it works[](#how-it-works)

Customer.io supports device-side metrics that help you determine the efficacy of your push notifications: `delivered` when a push notification is received by the app and `opened` when a push notification is clicked.

By default, our SDK automatically tracks `opened` and `delivered` events for push notifications originating from Customer.io, but you can add (optional) custom handling to various push events. You might need a custom handler for push notifications that originate outside of Customer.io or if you want to do something outside our SDK’s scope.

 Improve delivery metric reliability

Configure [App Groups](/integrations/sdk/expo/push-notifications/app-groups/) to make sure delivery metrics aren’t lost when iOS terminates the Notification Service Extension before the tracking request completes. With App Groups, the SDK automatically recovers any undelivered metrics on the next app launch.

 Do you use multiple push services in your app?

The Customer.io SDK only handles push notifications that originate from Customer.io. Push notifications that were sent from other push services or displayed locally on device are not handled by the Customer.io SDK. You must add custom handling logic to your app to handle those push events.

#### Choose whether to show push while your app is in the foreground[](#show-push-app-foreground)

If your app is in the foreground and the device receives a Customer.io push notification, your app gets to choose whether or not to display the push.

To configure this behavior, set the following option in your Expo config file:

```json
{
   ...
   "plugins": [
      ...
      [
        "customerio-expo-plugin",
        {
            "ios": {
                "pushNotification": {
                    "showPushAppInForeground": true,
                }
            }
         }
      ]
   ]
}
```

If the push did not come from Customer.io, you’ll need to [perform custom handling](#handle-push-received-foreground) to determine whether to display the push or not.

#### Custom handling when users click a push[](#handle-push-click)

You might need to perform custom handling when a user clicks a push notification—like you want to process custom fields in your push notification payload.

For now, the React Native SDK does not provide callbacks when your audience clicks a push notification. But you can use one of the many popular React Native push notification SDKs to receive a callback.

For example, the code below receives callbacks when users click a push using `react-native-push-notification`. Be sure to follow the documentation for the push notification SDK you choose to use to receive callbacks with.

```js
import { Notifications } from 'react-native-notifications';

Notifications.events().registerNotificationOpened((notification: Notification, completion) => {
  // Process custom data attached to payload, if you need: 
  let pushPayload = notification.payload; 

  // Important: When you're done processing the push notification, you're required to call completion(). 
  // Even if you do not process a push, this is still a requirement. 
  completion();
});
```

 Do you use deep links?

If you’re performing custom push click handling on push notifications originating from Customer.io, we recommend that you don’t launch a deep link URL yourself. Instead, let our SDK launch deep links to avoid unexpected behaviors.

#### Custom handling when getting a push while the app is foregrounded[](#handle-push-received-foreground)

If your app is in the foreground and you get a push notification, your app gets to choose whether or not to display the push. For push notifications originating from Customer.io, [your SDK configuration determines if you show the notification](#show-push-app-foreground). But you can add custom logic to your app when this kind of thing happens.

For now, the React Native SDK does not provide callbacks when a push notification is received and your app is in the foreground. But you can use one of the many popular React Native push notification SDKs to receive a callback.

For example, the code below receives a callback using `react-native-push-notification`. Be sure to follow the documentation for the push notification SDK you choose to use to receive callbacks with.

```js
import { Notifications } from 'react-native-notifications';

Notifications.events().registerNotificationReceivedForeground(
    (notification: Notification, completion) => {

  // Important: When you're done processing the push notification, you must call completion(). 
  // Even if you do not process a push, you must still call completion().  
  completion({ alert: true, sound: true, badge: true });

  // If the push notification originated from Customer.io, the value returned in the `completion` is ignored by the SDK. 
  // Use the SDK's push configuration options instead. 
});
```

## Manually track push metrics[](#manual-push-metrics)

 Avoid duplicate push metrics

If you manually track your own metrics, you should [disable automatic push tracking](#disabling-automatic-push-tracking) to avoid duplicate push metrics.

 Known issue tracking `opened` push metrics in app `killed` state

When manually tracking push metrics using Javascript methods, `opened` push metrics are *not tracked* when the app is in `killed` or `closed` state. This is a known behavior and it’s recommended to instead use the automatic push tracking feature.

To monitor the `delivered` push metrics of a received push notification, use the `CustomerIO.pushMessaging.trackNotificationReceived(<CUSTOMER.IO_PAYLOAD>)` method.

```javascript
CustomerIO.pushMessaging.trackNotificationReceived(<CUSTOMER.IO_PAYLOAD>)
```

To track `opened` push metrics, use the `CustomerIO.pushMessaging.trackNotificationResponseReceived(<CUSTOMER.IO_PAYLOAD>)` method.

```javascript
CustomerIO.pushMessaging.trackNotificationResponseReceived(<CUSTOMER.IO_PAYLOAD>)
```

The method that you use to retrieve the `<CUSTOMER.IO_PAYLOAD>` value depends on API of the SDK that you are using to receive push notifications from. Here is a code snippet as an example from `expo-notifications`:

```javascript
   // Listener called when a push notification is received
   Notifications.addNotificationReceivedListener(notification => {
      ...
      // Fetch Customer.io payload from the push notification
      const payload = notification.request.trigger.payload
      CustomerIO.pushMessaging.trackNotificationReceived(payload)
      ...
    });

   // Receives response when user interacts with the push notification
   Notifications.addNotificationResponseReceivedListener(response => {
      ...
      // Fetch Customer.io payload from the push notification response
      const payload = response.notification.request.trigger.payload
      CustomerIO.pushMessaging.trackNotificationResponseReceived(payload)
      ...
   });
```

## Disabling automatic push tracking[](#disabling-automatic-push-tracking)

If you have a more advanced use case and want to manually track push metrics, you can disable automatic push tracking with the `autoTrackPushEvents` option in your Expo config.

```json
{
   ...
   "plugins": [
      ...
      [
        "customerio-expo-plugin",
        {
            "ios": {
               "pushNotification": {
                  "autoTrackPushEvents": false,
               }
            }
         }
      ]
   ]
}
```

---

## Push notifications > Push notification channel

**Source:** /integrations/sdk/expo/push-notifications/push-notification-channel

# Android push notification channels

 🎉New in v2.4

Starting in Android 8.0, you can set up “notification channels,” which categorize notifications for your Android app. Every notification now belongs to a channel and the channel determines the behavior of notifications—whether they play sounds, appear as heads-up notifications, and so on. Channels also give users control over which channels they want to see notifications from. For example, if you had a news app, you might have different channels for sports, entertainment, and breaking news, giving users the ability to pick the channels they care about.

Today, Customer.io supports **a single channel per app**, and it has three settings, listed in the table below. You can customize your channel when you first set up the Customer.io SDK, but you cannot change the channel ID or importance level after you’ve created a channel. You can only change the channel name. [Learn more from the official Android developer docs](https://developer.android.com/develop/ui/views/notifications/channels).

Channels are created on the audience’s side when they receive their first push from Customer.io. Users can see your channel in their device settings.

Channel setting

Default

Description

Channel ID

`[your package name]`

The ID of the channel.

Channel name

`[your app name] Notifications`

The name of the channel.

Importance

`3`

The importance of the channel. Acceptable values are `0` (min), `1` (low), `2` (medium), `3` (default/high), and `4` (urgent). See [the Android developer documentation](https://developer.android.com/develop/ui/views/notifications/channels#importance) for more about the behavior of each importance level.

## Channel configuration[](#channel-configuration)

When you first integrate with the Customer.io SDK, you can set up your Android channel. Remember, after you’ve released a version of your app with channel settings, you can only change the channel name. Changes to other settings have no effect.

You’ll customize your channel in your app’s `app.json` file. See [Configuring the plugin](/integrations/sdk/expo/getting-started/packages-options/#configuring-the-expo-plugin) for more information about these fields.

```json
{
  "plugins": [
    [
      "expo-build-properties",
      {}
    ],
    [
      "customerio-expo-plugin",
      {
        "android": {
          "googleServicesFile": "./files/google-services.json",
          "setHighPriorityPushHandler": true,
          "pushNotification": {
            "channel": {
              "id": "channel_id_value",
              "name": "Channel Name",
              "importance": 4
            }
          }
        },
        "ios": {}
      }
    ]
  ]
}
```

### What channel settings can I change?[](#what-channel-settings-can-i-change)

When you first set up the Customer.io Expo SDK, you can customize your channel. But after you release a version of your app with the Customer.io SDK, you cannot change the channel ID or importance level. After that, you can only change the channel name. (This is a limitation imposed by Android, not Customer.io.)

If you released your app with a version of the Customer.io Expo SDK prior to 2.4.0, you can delete your old channel and create a new one with completely new settings per [Android’s developer documentation](https://developer.android.com/develop/ui/views/notifications/channels#DeleteChannel).

The chart below shows what channel settings you can or can’t change:

---

## Tracking > Anonymous activity

**Source:** /integrations/sdk/expo/tracking/anonymous-activity

# Anonymous activity

Before you identify a person, calls you make to the SDK are associated with an `anonymousId`. When you identify that person, we reconcile their anonymous activity with the identified person.

In Customer.io, you’ll see anonymous activity in the *Activity Log*, but we don’t surface anonymous [profilesAn instance of a person. Generally, a person is synonymous with their profile; there should be a one-to-one relationship between a real person and their profile in Customer.io. You reference a person’s profile attributes in liquid using `customer`—e.g. `{{customer.email}}`.](/merge-people) in Customer.io. You won’t be able to find an “anonymous person” in your workspace, and an anonymous person can’t trigger campaigns or get messages (including push notifications) from Customer.io.

When you identify a person, we merge anonymous activity with the identified person. And then the identified person’s previously-anonymous activity *can* trigger campaigns and cause your audience to receive messages.

For example, imagine that you have an ecommerce app, and you want to message people who view a specific product. An anonymous user looks at the product in question, goes to a different page, and then logs into your app. When they log in, we merge their anonymous activity including their `screen` view. This triggers the campaign you set up for people who visited the product page.

---

## Tracking > Identify

**Source:** /integrations/sdk/expo/tracking/identify

# Identify people

Use `CustomerIO.identify()` to identify a person. You need to identify a mobile user before you can send them messages or track events for things they do in your app.

## Identify a person[](#identify)

Identifying a person:

1.  Adds or updates the person in your workspace. This is basically the same as an [`identify` call to our server-side API](/api/#operation/identify).
    
2.  Saves the person’s information on the device. Future calls to the SDK reference the identified person. For example, after you identify a person, any events that you track are automatically associated with that person.
    
3.  Associates the current device token with the the person.
    

You can only identify one customer at a time. The SDK “remembers” the most recently-identified customer. If you identify person A, and then call the identify function for person B, the SDK “forgets” person A and assumes that person B is the current app user. You can also [stop identifying a person](#clearIdentify), which you might do when someone logs off or stops using your app for a significant period of time.

An identify request takes two parameters:

*   **userId** (Required): The unique value representing a person—an ID, email address, or the [cio\_idAn identifier for a person that is automatically generated by Customer.io and cannot be changed. This identifier provides a complete, unbroken record of a person across changes to their other identifiers (id, email, etc).](/identifying-people/#cio_id)
*   **traits** (Optional): An object containing [attributesA key-value pair that you associate with a person or an object—like a person’s name, the date they were created in your workspace, or a company’s billing date etc. Use attributes to target people and personalize messages.](/journeys/attributes/) that you want to add to, or update on, a person

```javascript
import { CustomerIO } from "customerio-reactnative";

// Call this method whenever you are ready to identify a user
CustomerIO.identify({
    userId: "user_id", 
    traits: {
        first_name: "user_name",
        email: "email_identifier",
    },
});
```

### Update a person’s attributes[](#update-person)

You store information about a person in Customer.io as [attributesA key-value pair that you associate with a person or an object—like a person’s name, the date they were created in your workspace, or a company’s billing date etc. Use attributes to target people and personalize messages.](/journeys/attributes/). When you call the `CustomerIO.identify()` function, you can update a person’s attributes on the server-side.

If a person is already identified, and then updates their preferences, provides additional information about themselves, or performs other attribute-changing actions, you can update their attributes with `setProfileAttributes`.

You only need to pass the attributes that you want to create or modify to `setProfileAttributes`. For example, if you identify a new person with the attribute `["first_name": "Dana"]`, and then you call `CustomerIO.setProfileAttributes = ["favorite_food": "pizza"]` after that, the person’s `first_name` attribute will still be `Dana`.

```javascript
const profileAttributes = {
    favouriteFood: "Pizza",
    favouriteDrink: "Mango Shake"
};
CustomerIO.setProfileAttributes(profileAttributes)    
```

### Device attributes[](#device-attributes)

By default (if you don’t set `.autoTrackDeviceAttributes(false)` in your config), the SDK automatically collects a series of [attributesA key-value pair that you associate with a person or an object—like a person’s name, the date they were created in your workspace, or a company’s billing date etc. Use attributes to target people and personalize messages.](/journeys/attributes/) for each device. You can use these attributes in [segmentsA group of people who match a series of conditions. People enter and exit the segment automatically when they match or stop matching conditions.](/journeys/data-driven-segments/) and other campaign workflow conditions to target the device owner, just like you would use a person’s other attributes. You cannot, however, use device attributes to personalize messages with [liquidA syntax that supports variables, letting you personalize messages for your audience. For example, if you want to reference a person’s first name, you might use the variable `{{customer.first_name}}`.](/using-liquid) yet.

Along with these attributes, we automatically set a `last_used` timestamp for each device indicating when the device owner was last identified, and the `last_status` of a push notification you sent to the device. You can also set your own custom device attributes. You’ll see a person’s devices and each device’s attributes when you go to **Journeys > People > Select a person**, and click **Devices**.

[![device attributes on a person's profile](https://customer.io/images/device-attributes.png)](#db82cfb11dbec1c42a9f938183dcedbd-lightbox)

 Your integration shows device attributes in the `context` object

When you inspect calls from the SDK (in your integration’s data inAn integration that feeds data *into* Customer.io. tab), you’ll see device information in the `context` object. We flatten the device attributes that you send into your workspace, so that they’re easier to use in [segmentsA segment is a group of people in your workspace. Use segments to trigger campaigns, track membership over time, or fine-tune your audience. There are two types of segments: data-driven and manual. Data-driven segments automatically update when people start or stop matching criteria. Manual segments are static.](/journeys/segments/). For example, `context.network.cellular` becomes `network_cellular`.

*   id string
    
    Required The device token.
    

#### Set custom device attributes[](#update-device)

You can also set custom device attributes with the `setDeviceAttributes` method. You might do this to save app preferences, time zones, or other custom values specific to the device. Like profile attributes, you can pass nested JSON to device attributes.

However, before you set custom device attributes, consider whether the attribute is specific to the `device` or if it applies to the person broadly. If you want an attribute to persist beyond the life of the device token, you should [apply it to the person](#update-person) rather than the device.

```javascript
const setDeviceAttributes = () => {
    const deviceAttributes = {
        type : "primary_device",
        parentObject : {
            childProperty : "someValue",
        },
    };
    CustomerIO.setDeviceAttributes(deviceAttributes)
}
```

#### Manually add device to profile[](#add-device)

In the standard flow, identifying a person automatically associates the token with the identified person in your workspace. If you need to manually add or update the device elsewhere in your code, call the method `CustomerIO.registerDeviceToken(token)`.

```javascript
const registerDevice = () => {
     // Customer.io expects a valid token to send push notifications
     // to the user.
     const token = 'token'

     CustomerIO.registerDeviceToken(token)
 }
```

## Stop identifying a person[](#clearIdentify)

When a person logs out, or does something else to tell you that they no longer want to be tracked, you should stop identifying them.

Use `clearIdentify()` to stop identifying the previously identified person (if there was one).

```javascript
CustomerIO.clearIdentify()
```

### Identify a different person[](#identify-a-different-person)

If you want to identify a new person—like when someone switches profiles on a streaming app, etc—you can simply call `identify()` for the new person. The new person then becomes the currently-identified person, with whom all new information—messages, events, etc—is associated.

```javascript
CustomerIO.identify( userId: "new.person@example.com", traits: {
    first_name: "New", 
    last_name: "Person"
}) 
```

---

## Tracking > Lifecycle events

**Source:** /integrations/sdk/expo/tracking/lifecycle-events

# Mobile Lifecycle events

By default, our Android SDK automatically tracks events that represent the lifecycle of your app and your users experiences with it.

By default, we track the following lifecycle events:

*   **Application Installed**: A user installed your app.
*   **Application Updated**: A user updated your app.
*   **Application Opened**: A user opened your app.
*   **Application Foregrounded**: A user switched back to your app.
*   **Application Backgrounded**: A user backgrounded your app or switched to another app.

You might also want to send your own lifecycle events, like `Application Crashed` or `Application Updated`. You can do this using the `track` method. You’ll find a list of properties for these events—both the ones we track automatically and other events you might send yourself—in our [Mobile App Lifecycle Event specification](/integrations/api/cdp/#section/Semantic-events-for-data-out-integrations/mobile-application-lifecycle-event-schemas).

## Lifecycle event examples[](#lifecycle-event-examples)

A lifecycle event is basically a `track` call that the SDK makes automatically for you.

When you look at your source data in Customer.io, you’ll see lifecycle events as `track` calls, where the event `properties` are specific to the name of the event. For example, the `Application Installed` event includes the app `version` and `build` properties.

```json
{
  "userId": "app.installer@example.com",
  "type": "track",
  "event": "Application Installed",
  "properties": {
    "version": "3.2.1", "build": "247"
  }
}
```

## Sending custom lifecycle events[](#sending-custom-lifecycle-events)

You can send your own lifecycle events using the `track` call. However, whenever you send lifecycle events, you should use the *Application EventName* convention that we use for our default lifecycle events.

These [semantic event](/cdp/sources/semantic-events/) names and properties represent [a standard](/cdp/sources/semantic-events/mobile-app/) that we use across Customer.io and our downstream destinations. Adhering to this standard ensures that your events automatically map to the correct event types in Customer.io and any other services you send your data to.

If you opt out of automatic lifecycle events, you can send your own `track` calls for these events. Or, for events we can’t track automatically, you might be able to use a webhook or a callback to collect crash events. For example, you might want to send a `track` call for `Application Crashed` when your app crashes or `Application Updated` when people update your app.

```javascript

CustomerIO.track("Application Crashed", { 
  url: "/page/in/app"
});
```

## Disable lifecycle events[](#disable-lifecycle-events)

We track lifecycle events by default. You can disable this behavior by passing the `trackApplicationLifecycleEvents` option in the `CioConfig` object when you initialize the SDK.

```javascript
import {
  CioLogLevel, CioRegion, CustomerIO, CioConfig
} from 'customerio-reactnative';
  
const config: CioConfig = {
    cdpApiKey: 'cdp_api_key', // Mandatory
    migrationSiteId: 'site_id', // For migration
    region: CioRegion.US,
    trackApplicationLifecycleEvents: false,
    inApp: {
	    siteId: 'site_id', // this removes the use of enableInApp and simplifies in-app configuration
    }
 };
CustomerIO.initialize(config)
```

---

## Tracking > Location

**Source:** /integrations/sdk/expo/tracking/location

# Location tracking

## How it works[](#how-it-works)

The Location module captures location (with user consent) from your app and attaches it to a person’s profile in Customer.io. You can use this data for geo-aware messaging and audience segmentation with more accuracy than [IP-based geolocation](/journeys/geolocation/).

When you identify a person, the SDK includes the latest location in the identify call. The SDK also sends a `Location Update` event to the person’s activity timeline, which you can use in journeys and segments. To balance location updates with battery and data usage, the SDK limits location updates once a day (at most)—and only sends that update when the person has moved a meaningful distance since the last update.

The SDK does not request location permission on its own—your app must handle the permission flow.

## Enable the location module[](#install)

Add `location.enabled` to your `customerio-expo-plugin` configuration in `app.json` or `app.config.js`. When you run the prebuild, the plugin automatically adds the required native dependencies on both iOS and Android—you don’t need to edit your Podfile or `build.gradle` manually.

```json
{
  "plugins": [
    [
      "customerio-expo-plugin",
      {
        "config": {
          "cdpApiKey": "<cdpApiKey>"
        },
        "android": {},
        "ios": {
          "pushNotification": {
            "provider": "apn",
            "useRichPush": true
          }
        },
        "location": {
          "enabled": true
        }
      }
    ]
  ]
}
```

After updating your configuration, run `npx expo prebuild` to regenerate the native project files with location support.

## Configure the tracking mode[](#configure-the-tracking-mode)

Add a `location` object to your `config` (for auto-initialization) or `CioConfig` (for manual initialization) to control how and when the SDK captures location.

Option

Type

Default

Description

`trackingMode`

`LocationTrackingMode`

`MANUAL`

Controls how and when the SDK captures location. See [tracking modes](#tracking-modes) below.

### Tracking modes[](#tracking-modes)

Mode

Description

`MANUAL`

Your app controls when it captures location. Call `setLastKnownLocation()` or `requestLocationUpdate()` to provide location. Use this when your app already has a location-tracking mechanism or you want full control over when you capture location data.

`ON_APP_START`

The SDK automatically captures a one-shot location once per app launch when your app enters the foreground. You can still call `setLastKnownLocation()` or `requestLocationUpdate()` alongside automatic capture. Use this for hands-off location tracking with minimal battery impact.

`OFF`

Disables location tracking entirely. All location calls become silent and location is not included in identify calls. Use this if you want to register the module but disable it at runtime.

 Auto-initialization (Expo 53+)

#### Auto-initialization (Expo 53+)[](#Auto-initialization \(Expo 53+\))

Set the tracking mode in the `config.location` object in your `app.json`:

```json
{
  "plugins": [
    [
      "customerio-expo-plugin",
      {
        "config": {
          "cdpApiKey": "<cdpApiKey>",
          "location": {
            "trackingMode": "MANUAL"
          }
        },
        "android": {},
        "ios": {
          "pushNotification": {
            "provider": "apn",
            "useRichPush": true
          }
        },
        "location": {
          "enabled": true
        }
      }
    ]
  ]
}
```

 Manual initialization

#### Manual initialization[](#Manual initialization)

Import `CioLocationTrackingMode` and set the tracking mode in your `CioConfig`:

```javascript
import {
  CustomerIO, CioConfig, CioLocationTrackingMode
} from 'customerio-reactnative';

const config: CioConfig = {
  cdpApiKey: 'your-cdp-api-key',
  // ...other config options
  location: {
    trackingMode: CioLocationTrackingMode.Manual,
  },
};

CustomerIO.initialize(config);
```

## Location APIs[](#location-apis)

The module provides two methods to capture location. You can call either method as often as you like; the SDK always caches the latest coordinates for profile enrichment, but sends a `Location Update` event no more than once a day—and only if the person has moved a meaningful distance since the last update. No matter how frequently you call these methods, the SDK throttles the updates for you so as not to overwhelm your workspace with profile updates.

### `setLastKnownLocation`[](#setlastknownlocation)

Pass coordinates directly from your app’s own location system. This doesn’t require any location permissions from the SDK. Your app manages location access independently of Customer.io.

Parameter

Type

Description

`latitude`

`number`

Latitude in degrees. Must be between -90 and 90.

`longitude`

`number`

Longitude in degrees. Must be between -180 and 180.

```javascript
import { CustomerIO } from 'customerio-reactnative';

// Pass coordinates from your app's location provider
CustomerIO.location.setLastKnownLocation(37.7749, -122.4194);
```

### `requestLocationUpdate`[](#requestlocationupdate)

Request a one-shot location from the native platform’s location services. Use this if your app doesn’t have its own location system. Your app must request location permission before calling this method—the SDK won’t prompt the user.

If a user doesn’t grant permission or location services are disabled, the request is ignored—no crash or exception. If a request is already in progress, additional calls are ignored until the current request completes.

1.  Add location permissions to your `app.json`:
    
    ```json
    {
      "expo": {
        "ios": {
          "infoPlist": {
            "NSLocationWhenInUseUsageDescription": "We use your location to personalize your experience."
          }
        },
        "android": {
          "permissions": [
            "ACCESS_COARSE_LOCATION",
            "ACCESS_FINE_LOCATION"
          ]
        }
      }
    }
    ```
    
2.  After your app requests and receives permission at runtime, call the SDK:
    
    ```javascript
    import { CustomerIO } from 'customerio-reactnative';
    
    // After location permission is granted
    CustomerIO.location.requestLocationUpdate();
    ```
    

 We recommend using a library like [expo-location](https://docs.expo.dev/versions/latest/sdk/location/) to handle permission requests in your Expo app.

## Profile switch behavior[](#profile-switch-behavior)

When you call `CustomerIO.clearIdentify()`, the SDK clears cached location data so that one person’s location doesn’t carry over to another person’s profile. The next person you identify starts with a clean slate.

Location persists across app restarts. When your app relaunches, the SDK restores the cached location so that the next `identify()` call includes it automatically.

---

## Tracking > Screen events

**Source:** /integrations/sdk/expo/tracking/screen-events

# Screen tracking

Screen views are events that record the pages that your audience visits in your app. They have a `type` property set to `screen`, and a `name` representing the title of the screen or page that a person visited in your app.

Screen view events let you trigger [campaignsCampaigns are automated workflows you set up to send people messages and perform other actions when they meet your criteria.](/journeys/campaigns-in-customerio/) or add people to [segmentsA group of people who match a series of conditions. People enter and exit the segment automatically when they match or stop matching conditions.](/journeys/data-driven-segments/) based on the parts of your app your audience uses. Screen view events also update your audience’s “Last Visited” attribute, which can help you track how recently people used your app.

## Enable automatic screen tracking[](#auto-screenview)

The example below shows one way to implement automatic screen tracking using Expo Router. Use this as inspiration—every app can have its own navigation patterns and screen tracking requirements.

**Not using Expo Router?** If your Expo project renders its own `NavigationContainer` with React Navigation, checkout our [React Native screen-tracking example](/integrations/sdk/react-native/tracking/screen-events/#auto-screenview) for ideas you can adapt to your own implementation.

If you want to send more data with screen events, or you don’t want to send events for every individual screen that people view in your app, you [send screen events manually](#manual-screenview).

### Requirements and limitations[](#requirements-and-limitations)

This approach requires:

*   **Expo Router**: This example only works with apps using Expo Router (not React Navigation or other navigation libraries). Learn more in the [Expo Router documentation](https://docs.expo.dev/router/introduction/).
*   **Expo SDK 49+**: The `usePathname` and `useGlobalSearchParams` hooks require recent Expo versions. Check the [Expo SDK upgrade guide](https://docs.expo.dev/workflow/upgrading-expo-sdk-walkthrough/) for migration details.
*   **File-based routing**: Your app must use Expo Router’s file-based routing structure. See the [file-based routing guide](https://docs.expo.dev/router/create-pages/) for setup instructions.

 This example works well with standard Expo Router setups. Since every app has unique navigation patterns and configurations, we recommend testing this implementation in your development environment first. You may need to adjust the code based on your specific Expo workflow (managed vs. bare) or custom routing needs. For navigation-specific questions or customizations, the Expo Router documentation and community forums are great resources.

With Expo Router, screen tracking is straightforward because it always has access to a URL. Create a higher-order component that observes the currently selected URL and tracks it in your analytics provider.

```javascript
import { useEffect } from 'react';
import { usePathname, useGlobalSearchParams, Slot } from 'expo-router';
import { CustomerIO } from 'customerio-reactnative';

export default function Layout() {
  const pathname = usePathname();
  const params = useGlobalSearchParams();

  // Track the location in your analytics provider
  useEffect(() => {
    CustomerIO.screen(pathname, params);
  }, [pathname, params]);

  // Export all the children routes
  return <Slot />;
}
```

Place this code in your `app/_layout.tsx` file to track screen changes throughout your app. **Test thoroughly** in your development environment before deploying.

### Screenview settings for in-app messages[](#screenview-settings-for-in-app-messages)

Customer.io uses `screen` events to determine where users are in your app so you can target them with in-app messages on specific screens. By default, the SDK sends `screen` events to Customer.io’s backend servers. But, if you don’t use `screen` events to track user activity, segment your audience, or to trigger campaigns, these events might constitute unnecessary traffic and event history.

If you don’t use `screen` events for anything other than in-app notifications, you can set the `ScreenViewUse` parameter to `ScreenView.InApp`. This setting stops the SDK from sending `screen` events back to Customer.io but still allows the SDK to use `screen` events for in-app messages, so you can target in-app messages to the right screen(s) without sending event traffic into Customer.io!

```javascript
import {
  CioLogLevel, CioRegion, CustomerIO, CioConfig
} from 'customerio-reactnative';

const App = () => {

useEffect(() => {
   const config: CioConfig = {
      cdpApiKey: 'CDP API Key', // Mandatory
      region: CioRegion.US,
      screenViewUse: ScreenView.All
      trackApplicationLifecycleEvents: true,
      inApp: {
         siteId: 'site_id',
      }
   };
   CustomerIO.initialize(config)
   }, [])
}
```

## Send your own screen events[](#manual-screenview)

Screen events use the `.screen` method. Like other event types, you can add a `data` object containing additional information about the event or the currently-identified person.

```javascript
CustomerIO.screen("screen-name", {"property": "value"})
```

---

## Tracking > Track events

**Source:** /integrations/sdk/expo/tracking/track-events

# Track events

Events represent things people do in your app so that you can track your audience’s activity and metrics. Use events to segment your audience, trigger campaigns, and capture usage metrics in your app.

## Track an event[](#track-an-event)

The `track` method helps you send events representing your audience’s activities to Customer.io. When you send events, you can include event `properties`—information about the person or the event that they performed.

In Customer.io, you can use events to trigger campaigns and broadcasts. Those campaigns might send someone a push notification or manipulate information associated with the person in your workspace.

Events include the following:

*   `name`: the name of the event. Most event-based searches in Customer.io hinge on the name, so make sure that you provide an event name that will make sense to other members of your team.
*   `properties` (Optional): Additional information that you might want to reference in a message. You can reference data attributes in messages and other campaign actionsA block in a campaign workflow—like a message, delay, or attribute change. using [liquidA syntax that supports variables, letting you personalize messages for your audience. For example, if you want to reference a person’s first name, you might use the variable `{{customer.first_name}}`.](/using-liquid) in the format `{{event.<attribute>}}`.

```javascript
import { CustomerIO } from "customerio-reactnative";

CustomerIO.track("event_name", { 
  propertyName: propertyValue 
});
```

 Perform downstream actions with semantic events

Some downstream actions don’t neatly map to our simple `identify`, `track`, and other calls. For these, we use “semantic events,” events that have a special meaning in Customer.io and your destinations. See [Semantic Events](#semantic-events) for more information.

### Anonymous activity[](#anonymous-activity)

If you send a `track` call before you `identify` a person, we’ll attribute the event to an `anonymousId`. When you identify the person, we’ll reconcile their anonymous activity with the identified person.

When we apply anonymous events to an identified person, the previously anonymous activity becomes eligible to trigger campaigns in Customer.io.

## Semantic Events[](#semantic-events)

Some actions don’t map cleanly to our simple `identify`, `track`, and other calls. For these, we use “semantic events,” events that have a special meaning in Customer.io and your destinations.

These are especially important in Customer.io for destructive operations like deleting a person. When you send an event with a semantic `event` name, we’ll perform the appropriate action.

For example, if a person decides to leave your service, you might delete them from your workspace. In Customer.io, you’ll do that with a `Delete Person` event.

```javascript
CustomerIO.track("User Deleted")
```

---

## Whats new > 2.x upgrade

**Source:** /integrations/sdk/expo/whats-new/2.x-upgrade

# Upgrade from version 1 to 2

This page helps you upgrade from the 1.0 version of our plugin to 2.0 so can take advantage of the latest features.

## What changed?[](#what-changed)

While this represents a significant change “under the hood,” we’ve tried to make it as seamless as possible for you; much of your implementation remains the same.

This move also adds two additional features:

*   **Support for anonymous tracking**: you can send events and other activity for anonymous users, and we’ll reconcile that activity with a person when you identify them.
*   **Built-in lifecycle events**: the SDK now automatically captures events like “Application Installed” and “Application Updated” for you.
*   **New device-level data**: the SDK captures the device `name` and other device-level context for you.

This change makes it easier to connect your app to both Customer.io and other destinations—like your analytics platform, data warehouse, or CRM. It provides customer data platform-like (CDP) features for your mobile app.

## Upgrade process[](#upgrade-process)

You’ll update initialization calls for the SDK itself and the push and/or in-app messaging modules.

**As a part of this process, your credentials change**. You’ll need to set up a new data integration in Customer.io and get a new *CDP API Key*. But you’ll *also* need to keep your previous `siteId` as a `migrationSiteId` when you initialize the SDK. The `migrationSiteId` is a key helps the SDK send remaining traffic when people update your app.

When you’re done, you’ll also need to change a few base properties to fit the new APIs. In general, `identifier` becomes `userId`, `body` becomes `traits`, and `data` becomes `properties`.

### 1\. Get your new *CDP API Key*[](#1-get-your-new-cdp-api-key)

The new version of the SDK requires you to set up a new data “integration” in Customer.io. As a part of this process, you’ll get your *CDP API Key*.

1.  Go to [**Data & Integrations** > **Integrations** and click the **Directory** tab](https://fly.customer.io/workspaces/last/journeys/integrations/all/directory).
2.  Find and select the **Expo** integration.
    
    [![The integration directory showing the expo integration](https://customer.io/images/expo-setup.png)](#82e5ea2130241fe735490e835b429c0a-lightbox)
    
3.  Enter a *Name* for the integration, like “My Expo App”.
4.  We’ll present you with a code sample containing a `cdpApiKey` that you’ll use to initialize the SDK. Copy this key and keep it handy. You’ll use it to initialize the SDK in your Expo app.
5.  (Optional) Set up your Expo app and click *Test Connection*.
6.  If you tested your connection first, click **Complete Setup**. Otherwise, click **Save & Complete Later**.
    
    [![The expo setup page with the integration named 'My Expo App'](https://customer.io/images/expo-find-in-directory.png)](#062f18a2b6c50bcd8d89c22c0a7f3630-lightbox)
    

### 2\. Update your app.json or app.config.js[](#2-update-your-appjson-or-appconfigjs)

We’ve changed some of the options for our plugin, so you’ll need to update your `app.json` or `app.config.js` file to use our new configuration settings. The major change is that you’ll need a new `cdpApiKey` (that you get in the [previous step](#1-get-your-new-cdp-api-key)); it replaces the `siteId` and `apiKey` parameters.

```json
{
   "plugins": [
      [
         "customerio-expo-plugin",
         {
            "android": {
               "googleServicesFile": "./files/google-services.json"
            },
            "ios": {
               "pushNotification": {
                  "useRichPush": true,
                  "env": {
                     "cdpApiKey": "<cdpApiKey>",
                     "region": "<region>"
                  },
               }
            }
         }
      ]
   ]
}
```

### 3\. Update your initialization[](#3-update-your-initialization)

You’ll initialize the new version of the SDK and its packages with `CioConfig` objects instead of `CustomerioConfig`. While we [have a list of all the new configuration options](/integrations/sdk/expo/getting-started/packages/options), you’ll want to pay close attention to the following changes:

*   `CustomerIOEnv` is no longer necessary.
*   `Region` becomes `CioRegion`.
*   `siteId` becomes `migrationSiteId`. This allows us to finish sending traffic to Customer.io as your audience upgrades to the new version of your app.
*   You’ll initialize the SDK with `initialize(config)` instead of `initialize(env, config)`.

```javascript
import {
  CioLogLevel, CioRegion, CustomerIO, CioConfig
} from 'customerio-reactnative';
  
const config: CioConfig = {
  cdpApiKey: 'cdp_api_key', // Mandatory
  migrationSiteId: 'site_id', // For migration
  region: CioRegion.US,
  logLevel: CioLogLevel.Debug,
  trackApplicationLifecycleEvents: true,
  inApp: {
    siteId: 'site_id', // this removes the use of enableInApp and simplifies in-app configuration
  }
};
CustomerIO.initialize(config)
```

### 4\. Update your `identify` call[](#4-update-your-identify-call)

Our APIs changed slightly in this release. We’ve done our best to make the new APIs as similar as possible to the old ones. The names of a few properties that you’ll pass in your calls have changed, but their functionality has not.

*   `identify`: `identifier` becomes `userId` and `body` becomes `traits`
*   `track` and `screen` calls are structured the same as previous versions, but the `data` object is now called `properties`.

We’ve highlighted changes in the sample below.

```javascript
//identify: identifier becomes userId, body becomes traits
CustomerIO.identify({
  userId: "user_id", 
  traits: {
    first_name: "user_name",
    email: "email_identifier",
  },
});

//track: no significant change to method 
//in Customer.io data object renamed properties
CustomerIO.track("track_event_name", { 
  propertyName: propertyValue 
});

//screen: no significant change to method.
//name becomes title, data object renamed properties
CustomerIO.screen("screen_event_name", { 
  propertyName: propertyValue 
});
```

## Initialization configuration changes[](#initialization-configuration-changes)

As a part of this release, we’ve changed a few configuration options when you initialize the SDK. You’ll use `CioConfig` to set your configuration options. The table below shows the changes to the configuration options. But you’ll find a complete list of configuration options—both for your app.json file and where you initialize the SDK—on the [Packages and configuration options](/integrations/sdk/expo/getting-started/packages) page

Field

Type

Default

Description

`cdpApiKey`

string

Replaces `apiKey`; required to initialize the SDK.

| `trackApplicationLifeCycleEvents` | boolean | `true` | When true, the SDK automatically tracks application lifecycle events (like *Application Installed*). | | `inApp` | object | | Replaces the former `enableInApp` option, providing a place to set in-app configuration options. For now, it takes a single property called `siteId`. |

---

## Whats new > 3.3 upgrade

**Source:** /integrations/sdk/expo/whats-new/3.3-upgrade

# 3.x -> 3.3

Version 3.3 of the Customer.io Expo plugin adds support for App Groups, improving push delivery metric tracking on iOS.

## What changed?[](#what-changed)

Version 3.3 introduces the `appGroupId` configuration option, which enables [App Groups](/integrations/sdk/expo/push-notifications/app-groups/) for reliable push delivery metric tracking on iOS.

This update also requires `customerio-reactnative` version 6.4.0 or later as a peer dependency.

## Do you need to update to this version?[](#do-you-need-to-update-to-this-version)

This update is additive—your existing integration works without changes. We recommend updating if you want to improve the reliability of push delivery metric tracking on iOS.

## Update process[](#update-process)

### 1\. Update the plugin version[](#1-update-the-plugin-version)

Update your `package.json` to use version 3.3 or later:

```bash
npm install customerio-expo-plugin@3.3 customerio-reactnative@6.4.0
# or
yarn add customerio-expo-plugin@3.3 customerio-reactnative@6.4.0
```

### 2\. Configure App Groups[](#2-configure-app-groups)

To take advantage of the new App Groups support, add `appGroupId` to your plugin configuration. See [App Groups for push tracking](/integrations/sdk/expo/push-notifications/app-groups/) for setup instructions.

### 3\. Rebuild your app[](#3-rebuild-your-app)

After updating the plugin, rebuild your app:

```bash
npx expo prebuild --clean
npx expo run:ios
```

---

## Whats new > 3.x upgrade

**Source:** /integrations/sdk/expo/whats-new/3.x-upgrade

# 2.x -> 3.x

Version 3.0.0 of the Customer.io Expo plugin requires the React Native new architecture.

## What changed?[](#what-changed)

Version 3.0.0 removes support for React Native’s legacy architecture. This aligns with [React Native’s move to exclusively support their new architecture](https://reactnative.dev/blog/2025/12/10/react-native-0.83). You must migrate your app to use React Native’s new architecture to use this and future versions of the plugin.

## Do you need to update to this version?[](#do-you-need-to-update-to-this-version)

We recommend updating to the latest plugin version. However, if your app uses the old React Native architecture and you’re not ready to migrate, you can continue using version 2.x until you’re ready to adopt the new architecture.

## Update process[](#update-process)

Updating to Customer.io Expo plugin version 3.0.0 is straightforward. There are no public API changes.

### 1\. Migrate to the new React Native architecture[](#1-migrate-to-the-new-react-native-architecture)

If you haven’t migrated to React Native’s new architecture yet, see the [React Native documentation](https://reactnative.dev/blog/2024/10/23/the-new-architecture-is-here) for instructions. When your app is successfully running on the new architecture, you can update to Customer.io Expo plugin version 3.0.0.

### 2\. Update the plugin version[](#2-update-the-plugin-version)

Update your `package.json` to use version 3.0.0 or later:

```bash
npm install customerio-expo-plugin@3.0.0
# or
yarn add customerio-expo-plugin@3.0.0
```

### 3\. Rebuild your app[](#3-rebuild-your-app)

After updating the plugin, rebuild your app:

```bash
# iOS
npx expo prebuild --clean
npx expo run:ios

# Android
npx expo prebuild --clean
npx expo run:android
```

### 4\. Test your integration[](#4-test-your-integration)

Since there are no public API changes, your existing Customer.io SDK calls will continue to work. However, you should test your app after updating to ensure everything works as expected.

## Troubleshooting[](#troubleshooting)

If you encounter build errors after updating, perform a clean rebuild:

```bash
# Clean and rebuild
npx expo prebuild --clean
npx expo run:ios
npx expo run:android
```

If issues persist, ensure your app is properly configured for React Native’s new architecture and that all dependencies support it.

 Try our MCP server!

Our MCP server includes an `integration` tool that can help you install and troubleshoot issues with our SDK, including problems with push and in-app notifications. See our [MCP server documentation](/ai/mcp-server/) for more information.

---

## Whats new > Changelog

**Source:** /integrations/sdk/expo/whats-new/changelog

# Changelog

Check out release history our Expo Plugin. Alpha and beta releases provide access new features and fixes that have been tested internally at Customer.io but have not been tested with a production app or audience. We strongly recommend that you use these versions internally, for acceptance testing or to get a head start integrating new features in your app.

#### Need to upgrade?

Select your current version to see all the features and fixes from your version to the latest release.

3.3.03.2.03.1.03.0.13.0.02.11.02.10.12.10.02.9.22.9.12.9.02.8.02.7.32.7.22.7.12.7.02.6.02.5.02.4.02.3.12.3.02.2.02.1.02.0.32.0.22.0.12.0.0-beta.22.0.0-beta.12.0.01.0.0-beta.171.0.0-beta.161.0.0-beta.151.0.0-beta.141.0.0-beta.131.0.0-beta.121.0.0-beta.111.0.0-beta.101.0.0-beta.91.0.0-beta.81.0.0-beta.71.0.0-beta.61.0.0-beta.51.0.0-beta.41.0.0-beta.31.0.0-beta.21.0.0-beta.1

### Breaking Changes

### Features

### Bug Fixes

# 3.x Releases[](#3x-releases)

* * *

*   ### 3.3.0[](#330)
    
    April 6, 2026[code changes](https://github.com/customerio/customerio-expo-plugin/compare/3.2.0...3.3.0)
    
    ### Features
    
    *   Push delivery reslience improvement ([#333](https://github.com/customerio/customerio-expo-plugin/issues/333)) ([791b847](https://github.com/customerio/customerio-expo-plugin/commit/791b84721ad04a02ac18f7b0e9f88a9f50a96e3a))
    

*   ### 3.2.0[](#320)
    
    March 12, 2026[code changes](https://github.com/customerio/customerio-expo-plugin/compare/3.1.0...3.2.0)
    
    ### Features
    
    *   Locaiton enrichment ([#328](https://github.com/customerio/customerio-expo-plugin/issues/328)) ([53db70e](https://github.com/customerio/customerio-expo-plugin/commit/53db70eab5d1eece55e63ea16a63ea05d59bc7a3))
    

*   ### 3.1.0[](#310)
    
    February 21, 2026[code changes](https://github.com/customerio/customerio-expo-plugin/compare/3.0.1...3.1.0)
    
    ### Features
    
    *   Added support for Notification Inbox ([#327](https://github.com/customerio/customerio-expo-plugin/issues/327)) ([e8bb3a7](https://github.com/customerio/customerio-expo-plugin/commit/e8bb3a7b18b2c679470dbc4dd5096e76c54dc68e))
    

*   ### 3.0.1[](#301)
    
    February 18, 2026[code changes](https://github.com/customerio/customerio-expo-plugin/compare/3.0.0...3.0.1)
    
    ### Bug Fixes
    
    *   Downgrade okhttp to v4.x ([#325](https://github.com/customerio/customerio-expo-plugin/issues/325)) ([60a5b89](https://github.com/customerio/customerio-expo-plugin/commit/60a5b898a8091f590a55051b56c596fdcb57926f))
    
*   ### 3.0.0[](#300)
    
    January 20, 2026[code changes](https://github.com/customerio/customerio-expo-plugin/compare/2.11.0...3.0.0)
    
    ### ⚠ BREAKING CHANGES
    
    *   remove React Native old architecture support (#323)
    
    ### Features
    
    *   remove React Native old architecture support ([#323](https://github.com/customerio/customerio-expo-plugin/issues/323)) ([0f96132](https://github.com/customerio/customerio-expo-plugin/commit/0f96132cf0f405e4c73c52397c4916ffaefc498c))
    
    #### RN
    
    *   Exposed `trackMetric` method for manually tracking push notification metrics. This is useful when using multiple push providers or displaying notifications without relying on Customer.io SDK. ([#539](https://github.com/customerio/customerio-reactnative/issues/539)) ([43deefe](https://github.com/customerio/customerio-reactnative/commit/43deefef5afe66161ed954948c5f0388ba79be37))
    *   In-app messages now support SSE (Server-Sent Events) as an alternative to polling, reducing latency and improving message delivery efficiency ([#555](https://github.com/customerio/customerio-reactnative/issues/555)) ([a777c69](https://github.com/customerio/customerio-reactnative/commit/a777c6958cc8b48116687a0a521a44fbee4582bd))
    

# 2.x Releases[](#2x-releases)

* * *

*   ### 2.11.0[](#2110)
    
    November 24, 2025[code changes](https://github.com/customerio/customerio-expo-plugin/compare/2.10.1...2.11.0)
    
    ### Features
    
    *   **auth:** DX-809 update to use preferred OIDC for npm publish in github actions [#321](https://github.com/customerio/customerio-expo-plugin/issues/321) ([#321](https://github.com/customerio/customerio-expo-plugin/issues/321)) ([d595723](https://github.com/customerio/customerio-expo-plugin/commit/d5957230c33ac0c984ec0b277b71f3122ae9eac0))
    

*   ### 2.10.1[](#2101)
    
    November 14, 2025[code changes](https://github.com/customerio/customerio-expo-plugin/compare/2.10.0...2.10.1)
    
    ### Bug Fixes
    
    *   in-app listener for android ([#320](https://github.com/customerio/customerio-expo-plugin/issues/320)) ([352cc5e](https://github.com/customerio/customerio-expo-plugin/commit/352cc5e1e964f4feece0c64dcb9c340eaf0e9de3))
    
*   ### 2.10.0[](#2100)
    
    October 31, 2025[code changes](https://github.com/customerio/customerio-expo-plugin/compare/2.9.2...2.10.0)
    
    ### Features
    
    *   Updated the SDK and dependencies for Android 16 compatibility, including minor updates to better support newer OS restrictions and behavior changes. ([#319](https://github.com/customerio/customerio-expo-plugin/issues/319)) ([2537f17](https://github.com/customerio/customerio-expo-plugin/commit/2537f17f77ff0b3f9a8bd376770767ab55280336))
    
    ### ⚠️ Notes
    
    *   Apps now may need to update their `compileSdk` version to `36` and Gradle version to at least `8.9.3` to ensure compatibility with updated dependencies and to successfully build against Android 16.
    *   Apps not using the required `compileSdk` or Gradle versions may experience issues. The plugin attempts to downgrade dependencies automatically. If issues persist, Android 16 support can be disabled by setting `android.disableAndroid16Support` to `true` in plugin configuration.
    

*   ### 2.9.2[](#292)
    
    October 24, 2025[code changes](https://github.com/customerio/customerio-expo-plugin/compare/2.9.1...2.9.2)
    
    ### Bug Fixes
    
    *   prevent message type crash in release builds ([#318](https://github.com/customerio/customerio-expo-plugin/issues/318)) ([71e40d7](https://github.com/customerio/customerio-expo-plugin/commit/71e40d7e84f3c1ee7618d05eddce6ef3d3ed9406))
    
*   ### 2.9.1[](#291)
    
    October 22, 2025[code changes](https://github.com/customerio/customerio-expo-plugin/compare/2.9.0...2.9.1)
    
    ### Bug Fixes
    
    *   remove deprecated maven dependency ([#316](https://github.com/customerio/customerio-expo-plugin/issues/316)) ([cb2f8b5](https://github.com/customerio/customerio-expo-plugin/commit/cb2f8b5ed93a8c03d65726550360a2c25be7b26d))
    
*   ### 2.9.0[](#290)
    
    October 20, 2025[code changes](https://github.com/customerio/customerio-expo-plugin/compare/2.8.0...2.9.0)
    
    ### Features
    
    *   Use latest RN SDK version and internally use CioFirebaseWrapper ([#314](https://github.com/customerio/customerio-expo-plugin/issues/314)) ([0fb27c3](https://github.com/customerio/customerio-expo-plugin/commit/0fb27c39aff5a19f4d7591b41459b967d871e5dc))
    

*   ### 2.8.0[](#280)
    
    October 9, 2025[code changes](https://github.com/customerio/customerio-expo-plugin/compare/2.7.3...2.8.0)
    
    ### Features
    
    *   You can now send banners, modals, pop-ups, and surveys to anonymous visitors —no ID or email required.
    *   Improve push notification delivery receipts delay for android ([#313](https://github.com/customerio/customerio-expo-plugin/issues/313)) ([ad96c18](https://github.com/customerio/customerio-expo-plugin/commit/ad96c183da6785a4cf79800b4ca0010f12c98d67))
    
    ### Breaking Features
    
    *   Support for Kotlin 2+ is added (#591) (b5f94ff)
    
    but this also means apps needs to have the following minimum requirement, which should already be covered in Expo 53 and above
    
    *   Gradle: 8.0 or later
    *   Android Gradle Plugin (AGP): 8.0 or later (8.2+ recommended)
    *   Kotlin: 1.9.20 or later (2.0+ required if using Kotlin Multiplatform or K2-specific features)
    
    Bug Fixes
    
    *   Resolved a crash when dismissing in app messages using back press during initial loading phase. Users can now safely navigate away from messages without encountering unexpected app crashes
    *   Fix in-app inline tabbed bug
    

*   ### 2.7.3[](#273)
    
    October 3, 2025[code changes](https://github.com/customerio/customerio-expo-plugin/compare/2.7.2...2.7.3)
    
    ### Bug Fixes
    
    *   Disabled native logging on armeabi devices using new architecture to prevent rare crashes from low level C++ code, no functional impact to end users. ([#312](https://github.com/customerio/customerio-expo-plugin/issues/312)) ([f970974](https://github.com/customerio/customerio-expo-plugin/commit/f970974f252adbd2b91bb10b59a34d5d0c822087))
    
*   ### 2.7.2[](#272)
    
    September 11, 2025[code changes](https://github.com/customerio/customerio-expo-plugin/compare/2.7.1...2.7.2)
    
    ### Bug Fixes
    
    *   Improved plugin logging and is now limited to node development environments by default and can be enabled manually by setting `CUSTOMERIO_DEBUG_MODE=true` in env ([#306](https://github.com/customerio/customerio-expo-plugin/issues/306)) ([aa32215](https://github.com/customerio/customerio-expo-plugin/commit/aa32215d5b58618e41f772a73ecb70cf44bafa45))
    *   Auto initialization `config` is now lenient and only runs during `prebuild`. To enforce strict checks, set `CUSTOMERIO_STRICT_MODE=true` in env ([#307](https://github.com/customerio/customerio-expo-plugin/issues/307)) ([d57d650](https://github.com/customerio/customerio-expo-plugin/commit/d57d650439e3617753e16968e2ee852284189341))
    *   Push config is now optional if app includes a valid `google-services.json` using other means, auto-initialization will work as long as the build succeeds ([#300](https://github.com/customerio/customerio-expo-plugin/issues/300)) ([548bc01](https://github.com/customerio/customerio-expo-plugin/commit/548bc0170c5ff4eef1ac6bdc3f74f1f384061244)) ([#309](https://github.com/customerio/customerio-expo-plugin/issues/309)) ([a69e62b](https://github.com/customerio/customerio-expo-plugin/commit/a69e62bb4c5bee96d0f69570dd5f34da10d53d90))
    
*   ### 2.7.1[](#271)
    
    September 9, 2025[code changes](https://github.com/customerio/customerio-expo-plugin/compare/2.7.0...2.7.1)
    
    ### Bug Fixes
    
    *   Removed the need to import React Native SDK header on iOS in auto generated code to resolve compatibility issues. ([#303](https://github.com/customerio/customerio-expo-plugin/issues/303)) ([0f16213](https://github.com/customerio/customerio-expo-plugin/commit/0f16213ac7c9bbc8223fe3eccade81e3a406d23c))
    
*   ### 2.7.0[](#270)
    
    August 29, 2025[code changes](https://github.com/customerio/customerio-expo-plugin/compare/2.6.0...2.7.0)
    
    ### Improvements
    
    *   Updated React Native SDK from `4.7.0` to `4.8.0` which includes following updates: ([#294](https://github.com/customerio/customerio-expo-plugin/issues/294)) ([a0e6190](https://github.com/customerio/customerio-expo-plugin/commit/a0e619043b5c7155bd3d73d5eef3bb6a6dc66d84))
        *   Added support for queueing sticky sessions on iOS
        *   Aligned iOS public API with other Customer.io SDK platforms
        *   Fixed build issues on Xcode 26 beta that only affected apps using CocoaPods
        *   Fixed an issue where custom scheme URLs were not opening when using FCM with `CioAppDelegateWrapper`
    *   When `setHighPriorityPushHandler` is explicitly set to `false`, Firebase Messaging service is now added to Android manifest with a lower priority (`-10`, previously `-1`) to reduce the chance of conflicts with other push handlers ([#297](https://github.com/customerio/customerio-expo-plugin/pull/297)) ([cb72527](https://github.com/customerio/customerio-expo-plugin/commit/cb725271925bf1fbad243f7c1709e9e4bcbbb4eb))
    

*   ### 2.6.0[](#260)
    
    August 25, 2025[code changes](https://github.com/customerio/customerio-expo-plugin/compare/2.5.0...2.6.0)
    
    ### ✨ Expo Plugin: Auto Initialization Support for React Native SDK
    
    *   Customer.io React Native SDK can now be configured directly from `app.json`, removing the need to call `CustomerIO.initialize()` manually in JavaScript. This update enables automatic SDK initialization at app startup when a valid `config` is present, requiring only the `cdpApiKey` (`region` is needed for `EU`, `siteId` for in-app messaging). It is compatible with Expo SDK 53+ and fully backward compatible, manual initialization continues to work without any warnings. Read more in [plugin docs](https://docs.customer.io/integrations/sdk/expo/quick-start-guide/). ([#292](https://github.com/customerio/customerio-expo-plugin/issues/292)) ([529306b](https://github.com/customerio/customerio-expo-plugin/commit/529306bc02c976d3d88c7444f63504043ff7f0a0))
    

*   ### 2.5.0[](#250)
    
    July 29, 2025[code changes](https://github.com/customerio/customerio-expo-plugin/compare/2.4.0...2.5.0)
    
    ### Utilizing React Native New Architecture
    
    *   This release fully adopts React Native’s new architecture using Fabric and TurboModules while maintaining compatibility with old architecture. No changes to public API, apps will automatically use appropriate setup based on their configuration. ([#278](https://github.com/customerio/customerio-expo-plugin/issues/278)) ([96e4069](https://github.com/customerio/customerio-expo-plugin/commit/96e406992aa019d61bcf4ae5f58f4c31ce3c1585))
    
    ## Fixes and Tooling update
    
    *   Support for FCM 12.x is added which requires a minimum iOS deployment target of 15. If you’re using FCM module, ensure your deployment target and tooling are up to date. Or lock Firebase to 11.x to avoid compatibility issues
    *   Fixes a crash when build attributes from device are nullable
    

*   ### 2.4.0[](#240)
    
    July 18, 2025[code changes](https://github.com/customerio/customerio-expo-plugin/compare/2.3.1...2.4.0)
    
    ### Features
    
    *   Add ability to configure messaging channels for local notifications ([#277](https://github.com/customerio/customerio-expo-plugin/issues/277)) ([4d7b620](https://github.com/customerio/customerio-expo-plugin/commit/4d7b620ed7979eaab85a58b0292d84f5993c0fdd))
    

*   ### 2.3.1[](#231)
    
    July 9, 2025[code changes](https://github.com/customerio/customerio-expo-plugin/compare/2.3.0...2.3.1)
    
    ### Bug Fixes
    
    *   Preserve numeric types when doing sanitization for JSON ([#276](https://github.com/customerio/customerio-expo-plugin/issues/276)) ([6384279](https://github.com/customerio/customerio-expo-plugin/commit/638427994c5302f6fad5413a053b0ff965dd16ef))
    
*   ### 2.3.0[](#230)
    
    July 1, 2025[code changes](https://github.com/customerio/customerio-expo-plugin/compare/2.2.0...2.3.0)
    
    ### Features
    
    *   Includes new system for accessing device-token and tracking notifications events. This replaces old Swizzling-based system, in iOS SDK. The change increases stability and improves compatibility with other SDKs (like Firebase) ([#273](https://github.com/customerio/customerio-expo-plugin/issues/273)) ([44fa3fc](https://github.com/customerio/customerio-expo-plugin/commit/44fa3fc1bb87be19760451d194f5449d7c093d8c))
    

*   ### 2.2.0[](#220)
    
    June 30, 2025[code changes](https://github.com/customerio/customerio-expo-plugin/compare/2.1.0...2.2.0)
    
    ### Features
    
    *   Added support for inline in-app messages. Inline in-app messages act like a part of the content on your page. They let you dynamically populate parts of your app and talk to your customers without interrupting their experience. ([#274](https://github.com/customerio/customerio-expo-plugin/issues/274)) ([8133e7d](https://github.com/customerio/customerio-expo-plugin/commit/8133e7d94c0d425f56edf73525b965c8b87da3c6))
    

*   ### 2.1.0[](#210)
    
    May 29, 2025[code changes](https://github.com/customerio/customerio-expo-plugin/compare/2.0.3...2.1.0)
    
    ## [2.1.0](https://github.com/customerio/customerio-expo-plugin/compare/2.0.3...2.1.0) (2025-05-29)
    
    ### Features
    
    *   Expo 53 support ([#268](https://github.com/customerio/customerio-expo-plugin/issues/268)) ([fba26e3](https://github.com/customerio/customerio-expo-plugin/commit/fba26e326b796b1783fffcfb4d974bdf93a96d98)) #264 #265 #266 *The CIO plugin now supports Expo 53 but it remains backward compatible for folks who aren’t ready to upgrade to Expo 53 yet*
    

*   ### 2.0.3[](#203)
    
    May 23, 2025[code changes](https://github.com/customerio/customerio-expo-plugin/compare/2.0.2...2.0.3)
    
    ### Bug Fixes
    
    *   sample apps workflows to run with xcode 16.2 ([#261](https://github.com/customerio/customerio-expo-plugin/issues/261)) ([7f17c03](https://github.com/customerio/customerio-expo-plugin/commit/7f17c0361d0925fcd18792b1de7e4db370130d9d))
    
*   ### 2.0.2[](#202)
    
    April 10, 2025[code changes](https://github.com/customerio/customerio-expo-plugin/compare/2.0.1...2.0.2)
    
    ### Bug Fixes
    
    *   Improves clarity and reduces configuration errors by grouping all iOS push notification settings under a unified pushNotification property; the previous configuration pattern has been deprecated (but not yet removed) and will be unsupported in future releases.([#247](https://github.com/customerio/customerio-expo-plugin/pull/247))
    
*   ### 2.0.1[](#201)
    
    April 3, 2025[code changes](https://github.com/customerio/customerio-expo-plugin/compare/2.0.0...2.0.1)
    
    Updates the React Native SDK from `4.2.2` to `4.2.3` which includes the following improvements.
    
    ### Bug Fixes
    
    *   \[iOS\] Fixed an issue where the “Application Installed” event was incorrectly triggered on every app launch instead of only after the initial installation.
    *   \[iOS\] Incorrectly scrolling content for in-app modal messages positioned top/bottom. [https://github.com/customerio/customerio-ios/pull/858](https://github.com/customerio/customerio-ios/pull/858)
    *   \[Android\] Resolved syncing issues for events stored while in battery saver (offline) mode ([https://github.com/customerio/customerio-android/pull/498](https://github.com/customerio/customerio-android/pull/498)) ([6f3d16f](https://github.com/customerio/customerio-android/commit/6f3d16fe01a675cfa522099230baf03650cf9c42))
    *   \[Android\] Fixed the sequencing of screen tracking events for in-app messaging current screen state ([https://github.com/customerio/customerio-android/pull/500](https://github.com/customerio/customerio-android/pull/500)) ([6877daf](https://github.com/customerio/customerio-android/commit/6877daf98235ce9c96a2ce4932f188efb2c33a71))
    
*   ### 2.0.0[](#200)
    
    March 26, 2025[code changes](https://docs.customer.io/sdk/expo/quick-start-guide/#configure-the-plugin)
    
    ### Features
    
    *   Added support FCM as push provider for iOS applications. All you need to do is specify FCM in the [app.json](https://docs.customer.io/sdk/expo/quick-start-guide/#configure-the-plugin) file for iOS. ([#224](https://github.com/customerio/customerio-expo-plugin/issues/224)) ([3c478e1](https://github.com/customerio/customerio-expo-plugin/commit/3c478e197a8a29b7ea72dcbe72a815c4e975dbd8))
    *   Enhanced support when using multiple push providers in the same application. You can learn more [here](https://docs.customer.io/sdk/expo/push-notifications/multiple-push-providers/).
    
*   ### 2.0.0-beta.2[](#200-beta2)
    
    March 13, 2025[code changes](https://github.com/customerio/customerio-expo-plugin/compare/2.0.0-beta.1...2.0.0-beta.2)
    
    ## [2.0.0-beta.2](https://github.com/customerio/customerio-expo-plugin/compare/2.0.0-beta.1...2.0.0-beta.2) (2025-03-13)
    
    ### Features
    
    *   Support FCM as push provider for iOS applications ([#224](https://github.com/customerio/customerio-expo-plugin/issues/224)) ([3c478e1](https://github.com/customerio/customerio-expo-plugin/commit/3c478e197a8a29b7ea72dcbe72a815c4e975dbd8))
    
*   ### 2.0.0-beta.1[](#200-beta1)
    
    December 24, 2024[code changes](https://github.com/customerio/customerio-expo-plugin/compare/1.0.0-beta.17...2.0.0-beta.1)
    
    ## [2.0.0-beta.1](https://github.com/customerio/customerio-expo-plugin/compare/1.0.0-beta.17...2.0.0-beta.1) (2024-12-24)
    
    ### ⚠ BREAKING CHANGES
    
    *   Data Pipelines Support: Sending your mobile data into our customer data platform (CDP) helps you support Journeys and the rest of your martech stack—analytics, data warehouses, CRMs, and more. (#190)
    
    > ***NOTE:*** Please follow the [migration guide](https://docs.customer.io/sdk/expo/whats-new/2.x-upgrade/) for a seamless upgrade to this version.
    
    ### Features
    
    *   Anonymous tracking: You can send anonymous events, and we’ll reconcile anonymous activity with your users when you identify them.
    *   Built-in lifecycle events: the SDK now automatically captures events like “Application Installed” and “Application Updated” for you, so you better understand your users’ behaviors in your app.
    *   New device data: The SDK captures complete device-level context, such as your audience’s screen dimensions, device names, and more.
    

# 1.x Releases[](#1x-releases)

* * *

*   ### 1.0.0-beta.17[](#100-beta17)
    
    November 21, 2024[code changes](https://github.com/customerio/customerio-expo-plugin/compare/1.0.0-beta.16...1.0.0-beta.17)
    
    ## [1.0.0-beta.17](https://github.com/customerio/customerio-expo-plugin/compare/1.0.0-beta.16...1.0.0-beta.17) (2024-11-21)
    
    ### Features
    
    *   Resolved compatibility issues with Expo SDK 52 for iOS apps by updating iOS deployment target to `15.1` from `13.4` to align with Expo 52 ([#156](https://github.com/customerio/customerio-expo-plugin/issues/156)) ([086b336](https://github.com/customerio/customerio-expo-plugin/commit/086b33679fc49978cc1256a91e66de6da582c9b0))
    
*   ### 1.0.0-beta.16[](#100-beta16)
    
    October 25, 2024[code changes](https://github.com/customerio/customerio-expo-plugin/compare/1.0.0-beta.15...1.0.0-beta.16)
    
    ## [1.0.0-beta.16](https://github.com/customerio/customerio-expo-plugin/compare/1.0.0-beta.15...1.0.0-beta.16) (2024-10-25)
    
    ### Bug Fixes
    
    *   Fixed compatibility issues with `expo-doctor` to prevent `PLUGIN_NOT_FOUND` error ([#154](https://github.com/customerio/customerio-expo-plugin/issues/154)) ([5b2adb3](https://github.com/customerio/customerio-expo-plugin/commit/5b2adb3ed251bc1c68328f504753df945c3b1d72))
    
*   ### 1.0.0-beta.15[](#100-beta15)
    
    February 21, 2024[code changes](https://github.com/customerio/customerio-expo-plugin/compare/1.0.0-beta.14...1.0.0-beta.15)
    
    ## [1.0.0-beta.15](https://github.com/customerio/customerio-expo-plugin/compare/1.0.0-beta.14...1.0.0-beta.15) (2024-02-21)
    
    ### Features
    
    *   expo 50 upgrade ([#127](https://github.com/customerio/customerio-expo-plugin/issues/127)) ([cea103b](https://github.com/customerio/customerio-expo-plugin/commit/cea103bfd18dc62b256859a8b8544bb582415448))
    
*   ### 1.0.0-beta.14[](#100-beta14)
    
    February 12, 2024[code changes](https://github.com/customerio/customerio-expo-plugin/compare/1.0.0-beta.13...1.0.0-beta.14)
    
    ## [1.0.0-beta.14](https://github.com/customerio/customerio-expo-plugin/compare/1.0.0-beta.13...1.0.0-beta.14) (2024-02-12)
    
    ### Features
    
    *   automatically handle push click events and increase compatibility with 3rd party push modules ([#112](https://github.com/customerio/customerio-expo-plugin/issues/112)) ([a67e345](https://github.com/customerio/customerio-expo-plugin/commit/a67e3454bd5510dea122c8e027b87707af4d5104))
    
*   ### 1.0.0-beta.13[](#100-beta13)
    
    August 7, 2023[code changes](https://github.com/customerio/customerio-expo-plugin/compare/1.0.0-beta.12...1.0.0-beta.13)
    
    ## [1.0.0-beta.13](https://github.com/customerio/customerio-expo-plugin/compare/1.0.0-beta.12...1.0.0-beta.13) (2023-08-07)
    
    ### Bug Fixes
    
    *   modified regex for Expo 48 and added UNUserNotificationCenter snippet ([#82](https://github.com/customerio/customerio-expo-plugin/issues/82)) ([e5cbc77](https://github.com/customerio/customerio-expo-plugin/commit/e5cbc77b3e3f12801dc27845abeb982a335d7572))
    
*   ### 1.0.0-beta.12[](#100-beta12)
    
    July 6, 2023[code changes](https://github.com/customerio/customerio-expo-plugin/compare/1.0.0-beta.11...1.0.0-beta.12)
    
    ## [1.0.0-beta.12](https://github.com/customerio/customerio-expo-plugin/compare/1.0.0-beta.11...1.0.0-beta.12) (2023-07-06)
    
    ### Bug Fixes
    
    *   path fix for monorepo support ([#91](https://github.com/customerio/customerio-expo-plugin/issues/91)) ([8f551d3](https://github.com/customerio/customerio-expo-plugin/commit/8f551d3948cf33267220b58d5c6271b97db15594))
    
*   ### 1.0.0-beta.11[](#100-beta11)
    
    July 3, 2023[code changes](https://github.com/customerio/customerio-expo-plugin/compare/1.0.0-beta.10...1.0.0-beta.11)
    
    ## [1.0.0-beta.11](https://github.com/customerio/customerio-expo-plugin/compare/1.0.0-beta.10...1.0.0-beta.11) (2023-07-03)
    
    This release uses the Customer.io React Native SDK version 3. After you install this version of the Expo plugin into your project, you will need to run `expo prebuild --clean` for changes to go into effect from the plugin.
    
    ### ⚠ BREAKING CHANGES
    
    *   auto-update native SDK and easier rich push install (#81)
    
    ### Bug Fixes
    
    *   auto-update native SDK and easier rich push install ([#81](https://github.com/customerio/customerio-expo-plugin/issues/81)) ([793e927](https://github.com/customerio/customerio-expo-plugin/commit/793e9274cf83cfbea6334957a1df2a96ccb80f2d))
    
*   ### 1.0.0-beta.10[](#100-beta10)
    
    June 1, 2023[code changes](https://github.com/customerio/customerio-expo-plugin/compare/1.0.0-beta.9...1.0.0-beta.10)
    
    ## [1.0.0-beta.10](https://github.com/customerio/customerio-expo-plugin/compare/1.0.0-beta.9...1.0.0-beta.10) (2023-06-01)
    
    ### Bug Fixes
    
    *   xcode race condition fix ([#76](https://github.com/customerio/customerio-expo-plugin/issues/76)) ([ff136ba](https://github.com/customerio/customerio-expo-plugin/commit/ff136ba21e08c4b63a17ad298c8fd71934108efc))
    
*   ### 1.0.0-beta.9[](#100-beta9)
    
    May 17, 2023[code changes](https://github.com/customerio/customerio-expo-plugin/compare/1.0.0-beta.8...1.0.0-beta.9)
    
    ## [1.0.0-beta.9](https://github.com/customerio/customerio-expo-plugin/compare/1.0.0-beta.8...1.0.0-beta.9) (2023-05-17)
    
    ### Bug Fixes
    
    *   compatibility for monorepos ([#67](https://github.com/customerio/customerio-expo-plugin/issues/67)) ([14fa86c](https://github.com/customerio/customerio-expo-plugin/commit/14fa86cab7b2c3465f7d1afe2906aef70c59af3b))
    
*   ### 1.0.0-beta.8[](#100-beta8)
    
    May 11, 2023[code changes](https://github.com/customerio/customerio-expo-plugin/compare/1.0.0-beta.7...1.0.0-beta.8)
    
    ## [1.0.0-beta.8](https://github.com/customerio/customerio-expo-plugin/compare/1.0.0-beta.7...1.0.0-beta.8) (2023-05-11)
    
    ### Bug Fixes
    
    *   expo-notifications compatibility with customerio-expo-plugin ([#70](https://github.com/customerio/customerio-expo-plugin/issues/70)) ([fe5cd0f](https://github.com/customerio/customerio-expo-plugin/commit/fe5cd0f5d0dd715e76b8bbfd656b4667eaf63dbf))
    
*   ### 1.0.0-beta.7[](#100-beta7)
    
    March 6, 2023[code changes](https://github.com/customerio/customerio-expo-plugin/compare/1.0.0-beta.6...1.0.0-beta.7)
    
    ## [1.0.0-beta.7](https://github.com/customerio/customerio-expo-plugin/compare/1.0.0-beta.6...1.0.0-beta.7) (2023-03-06)
    
    ### Features
    
    *   support for React Native and iOS SDK v2 ([#51](https://github.com/customerio/customerio-expo-plugin/issues/51)) ([65a76d9](https://github.com/customerio/customerio-expo-plugin/commit/65a76d98da8c1d012c0ff9f6bb697aea47d3b74f))
    
*   ### 1.0.0-beta.6[](#100-beta6)
    
    February 27, 2023[code changes](https://github.com/customerio/customerio-expo-plugin/compare/1.0.0-beta.5...1.0.0-beta.6)
    
    ## [1.0.0-beta.6](https://github.com/customerio/customerio-expo-plugin/compare/1.0.0-beta.5...1.0.0-beta.6) (2023-02-27)
    
    ### Bug Fixes
    
    *   github ref in npm publish step of deploy action ([#47](https://github.com/customerio/customerio-expo-plugin/issues/47)) ([59fb7f0](https://github.com/customerio/customerio-expo-plugin/commit/59fb7f0ad5ff05adb828166f4d62d96d18b08f9c))
    
*   ### 1.0.0-beta.5[](#100-beta5)
    
    February 27, 2023[code changes](https://github.com/customerio/customerio-expo-plugin/compare/1.0.0-beta.4...1.0.0-beta.5)
    
    ## [1.0.0-beta.5](https://github.com/customerio/customerio-expo-plugin/compare/1.0.0-beta.4...1.0.0-beta.5) (2023-02-27)
    
    ### Bug Fixes
    
    *   added dependencies ([#46](https://github.com/customerio/customerio-expo-plugin/issues/46)) ([5a2b643](https://github.com/customerio/customerio-expo-plugin/commit/5a2b643828e080a771fbd24795e818bada1c0e2f))
    
*   ### 1.0.0-beta.4[](#100-beta4)
    
    February 27, 2023[code changes](https://github.com/customerio/customerio-expo-plugin/compare/1.0.0-beta.3...1.0.0-beta.4)
    
    ## [1.0.0-beta.4](https://github.com/customerio/customerio-expo-plugin/compare/1.0.0-beta.3...1.0.0-beta.4) (2023-02-27)
    
    ### Features
    
    *   flexible notification request handling ([#40](https://github.com/customerio/customerio-expo-plugin/issues/40)) ([447a7c2](https://github.com/customerio/customerio-expo-plugin/commit/447a7c271d2da8d64ad12c5b16207c2c0500a45e))
    
*   ### 1.0.0-beta.3[](#100-beta3)
    
    February 20, 2023[code changes](https://github.com/customerio/customerio-expo-plugin/compare/1.0.0-beta.2...1.0.0-beta.3)
    
    ## [1.0.0-beta.3](https://github.com/customerio/customerio-expo-plugin/compare/1.0.0-beta.2...1.0.0-beta.3) (2023-02-20)
    
    ### Bug Fixes
    
    *   hot fixes ([#37](https://github.com/customerio/customerio-expo-plugin/issues/37)) ([4e1953c](https://github.com/customerio/customerio-expo-plugin/commit/4e1953c2ab77a04d0586e9afe72dcda7549bf684))
    
*   ### 1.0.0-beta.2[](#100-beta2)
    
    December 30, 2022[code changes](https://github.com/customerio/customerio-expo-plugin/compare/1.0.0-beta.1...1.0.0-beta.2)
    
    ## [1.0.0-beta.2](https://github.com/customerio/customerio-expo-plugin/compare/1.0.0-beta.1...1.0.0-beta.2) (2022-12-30)
    
    ### Bug Fixes
    
    *   removed restriction on peerDependencies to prevent issues on customer end ([#24](https://github.com/customerio/customerio-expo-plugin/issues/24)) ([bce90c0](https://github.com/customerio/customerio-expo-plugin/commit/bce90c0031c10707328c7a16f528f8536bd1c12e))
    
*   ### 1.0.0-beta.1[](#100-beta1)
    
    December 30, 2022[code changes](https://github.com/customerio/customerio-expo-plugin/issues/9)
    
    ## 1.0.0-beta.1 (2022-12-30)
    
    ### ⚠ BREAKING CHANGES
    
    *   changed googleServicesFilePath to googleServicesFile (#16)
    
    ### Features
    
    *   added ability to set use\_frameworks to static ([#9](https://github.com/customerio/customerio-expo-plugin/issues/9)) ([05d7012](https://github.com/customerio/customerio-expo-plugin/commit/05d7012dfdffe70aed7c93ef08fe3975c2440674))
    *   fixed issues with adding file to target ([#10](https://github.com/customerio/customerio-expo-plugin/issues/10)) ([0674c66](https://github.com/customerio/customerio-expo-plugin/commit/0674c6624dad61fdaa86800327c32f66a37470da))
    *   rich push environment variable setup ([#23](https://github.com/customerio/customerio-expo-plugin/issues/23)) ([8d83c95](https://github.com/customerio/customerio-expo-plugin/commit/8d83c955ea2f19d9d8ab9a10c069279f1f57db0e))
    *   user agent for expo ([#8](https://github.com/customerio/customerio-expo-plugin/issues/8)) ([19e78b1](https://github.com/customerio/customerio-expo-plugin/commit/19e78b1f00b04627004af4fa7f2a53c1b0f33ac8))
    
    ### Bug Fixes
    
    *   fixed info.plist formatting issue ([#18](https://github.com/customerio/customerio-expo-plugin/issues/18)) ([d701794](https://github.com/customerio/customerio-expo-plugin/commit/d70179445ae3fbd051b9d374b159dcb5207ca281))
    *   update .releaserc.json ([#21](https://github.com/customerio/customerio-expo-plugin/issues/21)) ([8c751cf](https://github.com/customerio/customerio-expo-plugin/commit/8c751cf221fdd2e63190af8be9a4716faac9783c))
    *   updated package version ([#19](https://github.com/customerio/customerio-expo-plugin/issues/19)) ([ab30383](https://github.com/customerio/customerio-expo-plugin/commit/ab303831c69c8bdee9e6831ebcce67539a6c7e5d))
    
    ### Code Refactoring
    
    *   changed googleServicesFilePath to googleServicesFile ([#16](https://github.com/customerio/customerio-expo-plugin/issues/16)) ([26be4db](https://github.com/customerio/customerio-expo-plugin/commit/26be4db53893432882b52b25c6040b40c76bca7b))
    

*    [3.x Releases](#3x-releases)
    *    [3.3](#33x-releases)
        *   [3.3.0](#330)
    *    [3.2](#32x-releases)
        *   [3.2.0](#320)
    *    [3.1](#31x-releases)
        *   [3.1.0](#310)
    *    [3.0](#30x-releases)
        *   [3.0.1](#301)
        *   [3.0.0](#300)

*    [2.x Releases](#2x-releases)
    *    [2.11](#211x-releases)
        *   [2.11.0](#2110)
    *    [2.10](#210x-releases)
        *   [2.10.1](#2101)
        *   [2.10.0](#2100)
    *    [2.9](#29x-releases)
        *   [2.9.2](#292)
        *   [2.9.1](#291)
        *   [2.9.0](#290)
    *    [2.8](#28x-releases)
        *   [2.8.0](#280)
    *    [2.7](#27x-releases)
        *   [2.7.3](#273)
        *   [2.7.2](#272)
        *   [2.7.1](#271)
        *   [2.7.0](#270)
    *    [2.6](#26x-releases)
        *   [2.6.0](#260)
    *    [2.5](#25x-releases)
        *   [2.5.0](#250)
    *    [2.4](#24x-releases)
        *   [2.4.0](#240)
    *    [2.3](#23x-releases)
        *   [2.3.1](#231)
        *   [2.3.0](#230)
    *    [2.2](#22x-releases)
        *   [2.2.0](#220)
    *    [2.1](#21x-releases)
        *   [2.1.0](#210)
    *    [2.0](#20x-releases)
        *   [2.0.3](#203)
        *   [2.0.2](#202)
        *   [2.0.1](#201)
        *   [2.0.0](#200)
        *   [2.0.0-beta.2](#200-beta2)
        *   [2.0.0-beta.1](#200-beta1)

*    [1.x Releases](#1x-releases)
    *    [1.0](#10x-releases)
        *   [1.0.0-beta.17](#100-beta17)
        *   [1.0.0-beta.16](#100-beta16)
        *   [1.0.0-beta.15](#100-beta15)
        *   [1.0.0-beta.14](#100-beta14)
        *   [1.0.0-beta.13](#100-beta13)
        *   [1.0.0-beta.12](#100-beta12)
        *   [1.0.0-beta.11](#100-beta11)
        *   [1.0.0-beta.10](#100-beta10)
        *   [1.0.0-beta.9](#100-beta9)
        *   [1.0.0-beta.8](#100-beta8)
        *   [1.0.0-beta.7](#100-beta7)
        *   [1.0.0-beta.6](#100-beta6)
        *   [1.0.0-beta.5](#100-beta5)
        *   [1.0.0-beta.4](#100-beta4)
        *   [1.0.0-beta.3](#100-beta3)
        *   [1.0.0-beta.2](#100-beta2)
        *   [1.0.0-beta.1](#100-beta1)

---

