The Klaviyo Expo plugin is a configuration plugin for Expo that automates the integration of Klaviyo's native SDKs into your Expo project. This plugin handles all the necessary native code modifications required for Klaviyo functionality, eliminating the need for manual native code changes.
The plugin is designed to work with the klaviyo-react-native-sdk and automates the setup of:
- Push notification open tracking
- Rich push notification support
- Badge count management (iOS)
- Key:value pair data reading
- Notification service extension setup (iOS)
- Icon / color notification configuration (Android)
- Universal links / App links support
- Your Expo app needs to be run as a development build. This plugin will not work in Expo Go.
minSdkVersionof23+compileSdkVersionof34+
- Minimum Deployment Target
13.0+ - Apple Push Notification Service (APNs) set up
⚠️ Important Note for Federated Apple Developer Accounts: If you're using a federated Apple Developer account, you'll need to provide an ASC API token with Admin access. While Expo supports federated accounts through ASC API tokens, the EAS CLI cannot directly log into federated accounts for credential management. See Expo's documentation on federated accounts for more details.
- Install the plugin in your Expo project. You need both
klaviyo-react-native-sdkandklaviyo-expo-pluginfor full functionality:
npm install klaviyo-react-native-sdk
npx expo install klaviyo-expo-plugin- Add the plugin to your
app.jsonorapp.config.js:
{
"expo": {
"plugins": [
//... other plugins
[
"klaviyo-expo-plugin",
{
"android": {
"logLevel": 1,
"openTracking": true,
"notificationIconFilePath": "./your/notification/icon/path.png",
"notificationColor": "#00FF00"
},
"ios": {
"badgeAutoclearing": true,
"codeSigningStyle": "Automatic",
"projectVersion": "1",
"marketingVersion": "1.0",
"devTeam": undefined // your devTeam ID here
}
}
]
]
}
}- Run a
prebuildto apply the Expo plugin to your project:
npx expo prebuild- (optional) We recommend using the
expo-notificationslibrary for push permissions, token retrieval, and reading push content. Check out our/exampleproject for some ideas on how to use this.
| Plugin prop | Type | Required | Description |
|---|---|---|---|
android.logLevel |
int | optional | Sets the logging level for the Klaviyo Android SDK. Default: 1 (DEBUG). Values: 0 (NONE), 1 (VERBOSE), 2 (DEBUG), 3 (INFO), 4 (WARNING), 5 (ERROR), 6 (ASSERT) |
android.openTracking |
boolean | optional | Enables tracking when notifications are opened. Default: true. Note that this is considered to be a dangerous mod, as it directly modifies your MainActivity code. |
android.notificationIconFilePath |
string | optional | Path to the notification icon file. Should be a white, transparent PNG. Default: none specified. Note that you should set this instead of expo-notifications, as they can conflict with each other. |
android.notificationColor |
string | optional | Hex color for notification accent. Must be a valid hex value, e.g., "#FF0000" Default: undefined |
ios.badgeAutoclearing |
boolean | optional | Enables automatic badge count clearing when app is opened. Default: true |
ios.codeSigningStyle |
string | optional | Declares management style for Code Signing Identity, Entitlements, and Provisioning Profile handled through XCode. Must be either "Manual" or "Automatic". Default: "Automatic". Note: We highly recommend using the automatic signing style. If you select manual, you may need to go into your developer.apple.com console and import the appropriate files and enable capabilities yourself. |
ios.projectVersion |
string | optional | The internal build number for version. Default: "1" |
ios.marketingVersion |
string | optional | The app version displayed in the App Store. Must be of the format "X.X" or "X.X.X". Default: "1.0" |
ios.devTeam |
string | optional | The 10-digit alphanumeric Apple Development Team ID associated with the necessary signing capabilites, provisioning profile, etc. Format: "XXXXXXXXXX" Default: undefined |
Note: If you do not need to specify any of these for your project, it will use the defaults defined here. If you do not specify any of these props, you can add the plugin without additional arguments:
{
"expo": {
"plugins": [
//... other plugins
"klaviyo-expo-plugin"
]
}
}
If you'd like to see debug logs of your prebuild, add the following to your build command:
EXPO_DEBUG=true npx expo prebuild
EXPO_DEBUG=true npx expo run:platformPlease attach these to any build issues you have to help the team debug.
In your configuration file, set the following:
| Property | Details |
|---|---|
version |
Your app version. Corresponds to CFBundleShortVersionString on iOS. Format: "X.X.X" (e.g. "1.0" or "2.3.1") |
ios.buildNumber |
Build number for your iOS app. Corresponds to CFBundleVersion. Format: "42" |
ios.bundleIdentifier |
Bundle identifier for your iOS app. Format: "com.companyname.appname" |
ios.infoPlist.UIBackgroundModes |
set this to ["remote-notification"] to ensure you can receive background push notifications |
android.package |
Package name for your Android app. Format: "com.companyname.appname" |
These values are used in various native configuration files and must be properly set for the plugin to work correctly.
In addition, this plugin adds a KlaviyoNotificationServiceExtension target to support Notification Service Extension capabilities on iOS and utilizes app groups to sync and share data across the Klaviyo SDK modules. To support this added target and app group in your Expo configuration file, add:
"appExtensions": [
{
"targetName": "KlaviyoNotificationServiceExtension",
"bundleIdentifier": "{YOUR_BUNDLE_ID}.KlaviyoNotificationServiceExtension",
"entitlements": {
"com.apple.security.application-groups": ["group.{YOUR_BUNDLE_ID}.KlaviyoNotificationServiceExtension.shared"]
}
}
]
to the extra.eas.build.experimental.ios body of your app config where {YOUR_BUNDLE_ID} is your own app's bundle id. (See our example app config.)
Universal Links (iOS) and App Links (Android) allow you to navigate to a particular page within your app when users tap on links from outside your app, such as from emails, websites, or push notifications. Klaviyo uses universal links for click tracking in email campaigns, allowing you to track user engagement while seamlessly directing them to your app.
The Klaviyo SDK provides the handleUniversalTrackingLink() method to automatically handle click tracking for Klaviyo universal links and redirect users to the intended destination within your app.
⚠️ Important: Universal link tracking requiresklaviyo-react-native-sdkversion 2.1.0 or higher. Make sure you have the correct version installed:npm install klaviyo-react-native-sdk@^2.1.0
To enable universal links on iOS, you need to configure Associated Domains in your app configuration:
- Add the
associatedDomainsarray to youriosconfiguration inapp.json:
{
"expo": {
"ios": {
"bundleIdentifier": "com.yourcompany.yourapp",
"associatedDomains": [
"applinks:trk.your-domain.com"
]
}
}
}- Verify domain ownership by hosting an
apple-app-site-associationfile on your domain. This file must be accessible athttps://trk.your-domain.com/.well-known/apple-app-site-associationorhttps://trk.your-domain.com/apple-app-site-association.
For more details on setting up Associated Domains, see Apple's documentation.
To enable App Links on Android, you need to configure intent filters in your app configuration:
- Add intent filters to your
androidconfiguration inapp.json:
{
"expo": {
"android": {
"package": "com.yourcompany.yourapp",
"intentFilters": [
{
"action": "VIEW",
"autoVerify": true,
"data": [
{
"scheme": "https",
"host": "trk.your-domain.com",
"pathPrefix": "/u"
}
],
"category": ["BROWSABLE", "DEFAULT"]
}
]
}
}
}- Verify domain ownership by hosting a Digital Asset Links JSON file on your domain. This file must be accessible at
https://trk.your-domain.com/.well-known/assetlinks.json.
For more details on setting up Android App Links, see Android's documentation.
In your React Native code, use Expo's expo-linking library to handle both universal links and custom deep links:
import { useEffect } from 'react';
import * as Linking from 'expo-linking';
import { Klaviyo } from 'klaviyo-react-native-sdk';
export default function App() {
useEffect(() => {
const handleUrl = (url: string) => {
// Check if this is a Klaviyo universal tracking link
if (Klaviyo.handleUniversalTrackingLink(url)) {
// Klaviyo SDK is handling the tracking link
// It will track the click and redirect to the destination URL
console.log('Klaviyo tracking link processed:', url);
return;
}
// Handle other deep links in your app (both custom schemes and universal links)
// This will be called after Klaviyo redirects from the tracking link to your destination
console.log('Navigating to destination:', url);
// Add your navigation logic here (e.g., using React Navigation)
};
// Handle the initial URL if the app was opened with a link
Linking.getInitialURL().then((url) => {
if (url) {
handleUrl(url);
}
});
// Listen for deep link events while the app is running
const subscription = Linking.addEventListener('url', ({ url }) => {
handleUrl(url);
});
return () => {
subscription.remove();
};
}, []);
// Rest of your app code
return (
// Your app components
);
}How it works:
- When a user taps a Klaviyo tracking link (format:
https://trk.your-domain.com/u/...), your app opens - The
handleUrlfunction receives the URL Klaviyo.handleUniversalTrackingLink()checks if it's a Klaviyo tracking link:- If true: The SDK tracks the click event and resolves the tracking link to its destination URL, which triggers another call to
handleUrlwith the destination - If false: The URL is not a Klaviyo tracking link, so you should handle it as a regular deep link
- If true: The SDK tracks the click event and resolves the tracking link to its destination URL, which triggers another call to
- Your app navigates to the appropriate screen based on the destination URL
For more information on deep linking with Expo, see Expo's Linking documentation.
We created an example app to show how to use this plugin in coordination with the klaviyo-react-native-sdk. Set this up and run based on whichever platform you'd like to test on:
// from repo directory
cd example
npm run clean-android // for android
npm run clean-ios // for ios
npm run reset-all // deletes node modules and built foldersCommon issues and solutions:
-
Push Notifications Not Working
- Verify FCM/APNs setup
- Check notification permissions
- Ensure proper configuration in app.json
- You may need to add the
aps-environmentto yourios.entitlementsin your app config if it is not there alreadyios: { entitlements: { 'aps-environment': 'development', // or 'production' // ... other entitlements } }
- You may need to add the
-
Deep Links / Universal Links Not Working
- Custom URL schemes: Verify URL scheme configuration in your app.json
- Universal Links (iOS):
- Verify
associatedDomainsis configured correctly in your app.json - Ensure your
apple-app-site-associationfile is properly hosted and accessible - Test the domain verification using Apple's app-site-association verification tool
- Make sure your domain supports HTTPS
- Verify
- App Links (Android):
- Check intent filters are configured with
autoVerify: truein your app.json - Ensure your
assetlinks.jsonfile is properly hosted athttps://your-domain.com/.well-known/assetlinks.json - Test the domain verification using the command:
adb shell pm get-app-links your.package.name - Verify the domain supports HTTPS
- Check intent filters are configured with
- Testing Universal Links:
- Universal links will NOT work when tapped from within the same app or in simulator under certain conditions
- Test by sending yourself an email with the link or using the Notes app
- On Android, use
adb shell am start -a android.intent.action.VIEW -d "https://your-tracking-link"to test
-
Build Errors
- Clean and rebuild the project
- Verify Expo SDK version compatibility
- Check native dependencies
-
EAS Errors
- (iOS) Check your Identifiers in the Apple Developer Console and ensure the main app target has Push Notifications and App Group capabilities checked, and your Notification Service Extension target has App Groups capability checked. Check that both App Group capabilities have the app group name with the appended
.KlaviyoNotificationServiceExtension.sharedsuffix - (iOS) You should have two different provisioning profiles generated for this project. One for the main target, one for the Notification Service Extension.
- (iOS) Ensure the correct provisioning profiles are being recognized by EAS, and declare the Notification Service Extension in the
extra.eas.build.experimental.ios.appExtensionsof your app config as mentioned here
- (iOS) Check your Identifiers in the Apple Developer Console and ensure the main app target has Push Notifications and App Group capabilities checked, and your Notification Service Extension target has App Groups capability checked. Check that both App Group capabilities have the app group name with the appended
The Klaviyo Expo Plugin is available under the terms of the MIT license.