React Native SDK
Integrate the WarpLink React Native SDK to add deep linking, deferred deep links, and install attribution across iOS and Android.
Requirements: React Native 0.75+, React 18+, iOS 15+ and/or Android API 26+. This SDK requires native modules and does not work with Expo Go. Use a development build.
Source: github.com/WarpLinkApp/warplink-react-native-sdk
Installation
npm install @warplink/react-native
cd ios && pod installnpx expo install @warplink/react-native
npx expo prebuildPlatform Setup
The React Native SDK bridges to native iOS and Android SDKs. Both platforms need configuration.
iOS
- Open Xcode, select your app target > Signing & Capabilities
- Add Associated Domains >
applinks:aplnk.to - Enable Associated Domains in your App ID at developer.apple.com
For Expo, add to app.json:
{
"expo": {
"ios": {
"associatedDomains": ["applinks:aplnk.to"]
}
}
}
Android
Add the intent filter to your main Activity in android/app/src/main/AndroidManifest.xml:
<intent-filter android:autoVerify="true">
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="https" android:host="aplnk.to" />
</intent-filter>
For Expo, add to app.json:
{
"expo": {
"android": {
"intentFilters": [{
"action": "VIEW",
"autoVerify": true,
"data": [{ "scheme": "https", "host": "aplnk.to" }],
"category": ["BROWSABLE", "DEFAULT"]
}]
}
}
}
Serving links from a custom domain? Add it to the associated domains and the intent filter alongside aplnk.to, then declare it in configure().
iOS AppDelegate Hook
iOS does not hand Universal Links to native modules on its own. Add the WarpLink call to the delegate methods you already have, and leave their return values to your existing linking code.
handleIncomingURL returns nothing and does no domain filtering, so never let it decide your delegate's return value. Keep returning whatever your existing forwarding returns, or the deep links your app already handles stop arriving. Forwarding every URL is safe: the SDK tries to resolve each one and silently drops anything that is not a WarpLink link, so custom schemes and OAuth callbacks never reach the onLink you passed to configure(). An explicit onDeepLink() subscriber can still receive them, as an { error } event with code E_INVALID_URL.
// ios/YourApp/AppDelegate.swift
import React
import warplink_react_native
// Universal Links (https://): cold start and warm start
func application(
_ application: UIApplication,
continue userActivity: NSUserActivity,
restorationHandler: @escaping ([UIUserActivityRestoring]?) -> Void
) -> Bool {
if userActivity.activityType == NSUserActivityTypeBrowsingWeb,
let url = userActivity.webpageURL {
WarpLinkModule.handleIncomingURL(url)
}
return RCTLinkingManager.application(
application, continue: userActivity, restorationHandler: restorationHandler)
}
// Custom URL schemes (myapp://): only if your app uses them
func application(
_ application: UIApplication,
open url: URL,
options: [UIApplication.OpenURLOptionsKey: Any] = [:]
) -> Bool {
WarpLinkModule.handleIncomingURL(url)
return RCTLinkingManager.application(application, open: url, options: options)
}// ios/YourApp/AppDelegate.mm
#import <React/RCTLinkingManager.h>
#import <warplink_react_native/warplink_react_native-Swift.h>
// Universal Links (https://): cold start and warm start
- (BOOL)application:(UIApplication *)application
continueUserActivity:(NSUserActivity *)userActivity
restorationHandler:(void (^)(NSArray<id<UIUserActivityRestoring>> *))restorationHandler
{
if ([userActivity.activityType isEqualToString:NSUserActivityTypeBrowsingWeb] &&
userActivity.webpageURL != nil) {
[WarpLinkModule handleIncomingURL:userActivity.webpageURL];
}
return [RCTLinkingManager application:application
continueUserActivity:userActivity
restorationHandler:restorationHandler];
}
// Custom URL schemes (myapp://): only if your app uses them
- (BOOL)application:(UIApplication *)application
openURL:(NSURL *)url
options:(NSDictionary<UIApplicationOpenURLOptionsKey, id> *)options
{
[WarpLinkModule handleIncomingURL:url];
return [RCTLinkingManager application:application openURL:url options:options];
}The Swift module name is the pod name with hyphens replaced by underscores (warplink_react_native). If your delegate forwards to super as well as a linking library, keep every call and store each result in a local before you combine them. A || chain short-circuits, so a handler placed after one that returns true never runs.
Expo
There is no WarpLink config plugin, and expo prebuild regenerates the AppDelegate from the template, so a call you added by hand is lost on the next prebuild. Pick one:
- Commit the generated
ios/directory and add the calls to theAppDelegatethere. - Add the calls from an Expo config plugin with
withAppDelegate, so they survive every prebuild.
Expo Go cannot load native modules, so a development build is required either way.
Android needs no host code: configure() handles cold start automatically, and warm start works once your launch Activity uses android:launchMode="singleTask".
Configure the SDK
Call configure() once at app startup, outside of any React component. Pass an onLink callback and the SDK wires cold start, warm start, and the deferred deep link check into that single sink.
The credential below is an SDK key, created in the dashboard under API Keys > SDK key. An API key looks identical but cannot record installs: deep links still resolve, so the integration looks healthy while every attribution call is rejected.
import { WarpLink } from '@warplink/react-native';
WarpLink.configure({
apiKey: 'wl_live_yoursdkkeyhere000000000000000000',
debugLogging: true, // Enable native console logging
onLink: ({ deepLink, error }) => {
if (error || !deepLink) return; // resolution or config failure
// Fired for taps AND deferred installs. Check deepLink.isDeferred.
navigateTo(deepLink.destination);
},
});
configure() returns a Promise<void> and does not throw on a malformed key. It validates the key format, reports a bad one through onLink as { error }, and delegates server validation to the native SDK. await it if you want to catch native configuration errors; those surface through onLink as { error } too.
Declare Your Link Domains
The SDK always recognizes aplnk.to. If your links are served from a custom domain, list it in linkDomains. configure() passes the list straight to the native iOS and Android SDKs, so a link on that domain resolves on the very first launch, with no network round trip.
WarpLink.configure({
apiKey: 'wl_live_yoursdkkeyhere000000000000000000',
linkDomains: ['links.yourapp.com', 'go.yourapp.com'],
onLink: ({ deepLink }) => deepLink && navigateTo(deepLink.destination),
});
Without it, the native side only learns your domain after it has reached the server, and the decision to claim a URL is made synchronously before that answer arrives. That is why a fresh install can hand your own link back to your router.
The native declaration works too, and is read before any JavaScript runs: a WarpLinkDomains array in Info.plist on iOS, an app.warplink.DOMAINS meta-data entry in AndroidManifest.xml on Android. See the iOS and Android guides. Expo apps can set the iOS key through expo.ios.infoPlist in app.json, and the Android entry from a config plugin.
Every form is optional and additive. Domains are normalized natively (trimmed, lowercased, a full URL reduced to its host), so paste whichever form you have.
Opt Out of Automatic Handling
Both pieces are on by default. Disable either to drive it yourself with the manual methods:
WarpLink.configure({
apiKey: 'wl_live_yoursdkkeyhere000000000000000000',
automaticDeepLinks: false, // use onDeepLink / getInitialDeepLink yourself
automaticDeferredDeepLinks: false, // use checkDeferredDeepLink yourself
});
Next Steps
- Deep Links: Cold start, warm start, and React Navigation
- Deferred Deep Links: Preserve context through installs
- Attribution: Platform-specific matching and privacy
- API Reference: Full TypeScript API documentation