
iOS | Android | RN >0.63
This article complements the Webinar on Deep Linking that can be found here (in Polish). If you want to know more, check out the video!
What Deep Linking is?
Imagine that you have an app that clones Medium’s functionality and allows users to add and read articles. Now, users probably want to share or promote a good content, but how can you help them navigate directly to a specific article? This is where Deep Links comes into play!
So now we know that Deep Link is a simple URI that directs a user to desired content rather than just launching the home page/main screen.
The most basic form (and easiest to implement) of a Deep Link in mobile apps is a custom URI scheme. For example, you can define a scheme like vd:// for your app that launches the app, but something like vd://careers/react-native will take a user to the screen with React Native career description.
Example schemes for well-known apps:
- fb://
- slack://
- twitter://
- comgooglemaps://
There are also build-in schemes like:
- http:// | https://
- mailto://
- tel://
- sms://
Your app can have any scheme and it’s not unique (like bundle ID) or verified. This means that you can have two apps installed on your phone that use the same scheme. This is also a vulnerable part of custom schemes, because it allows malware apps to try to intercept information transmitted to your application via a Deep Link. Another disadvantage is that custom schemes do not handle any fallbacks in cases where your app is not installed. It simply will not work.
This is why another approach came up (and currently is strongly recommended by Apple) which is using a traditional http:// link that opens a web browser. First of all, it is safer because it requires that you own the domain to which links are redirecting. It also handles the process if the user does not have your app installed — it just opens the website in the browser.
On iOS, these links are called Universal Links, and on Android, App Links.
Now, let’s dive straight into implementation. You can use this tutorial to implement Deep Links in your already existent app or you can run ‘npx react-native init TestDeepLinking’ to follow.
iOS Custom Scheme and Universal Links
Official docs: https://developer.apple.com/library/archive/documentation/General/Conceptual/AppSearch/UniversalLinks.html
If you’ll try to run your custom scheme in Safari on your device or in simulator, you’ll see something like this:

Support custom scheme
To support a custom scheme, let’s say vd:// we need to perform 2 steps.
Step I - Add custom scheme in XCode
To do this, go to your Target Info and add a new URL Type. As an ‘Identifier’ you can use your bundle ID, for ‘URL Scheme’ you can use whatever you want. We will use vd. As ‘Role’ in this case leave ‘Editor’, but if you’re going to invoke other app’s schemes from within your app (eg. mailto://), then you would change it to ‘Viewer’.

Step II - Add code in AppDelegate.m
Go to AppDelegate.m and add this openURL method that handles incoming URLs using RCTLinkingManager provided by React Native. Please note that official docs will stress that you need to link this library, but in this version of React Native this is no longer needed and you are good to go.
#import <React/RCTLinkingManager.h>
- (BOOL)application:(UIApplication *)application
openURL:(NSURL *)url
options:(NSDictionary<UIApplicationOpenURLOptionsKey,id> *)options
{
return [RCTLinkingManager application:application openURL:url options:options];
}
More:
https://developer.apple.com/documentation/uikit/uiapplication/1622961-openurl
Test it
Build your app. When you make native changes you have to rebuild it to see the effect of your work. Regular refresh won’t be enough.
Now, test if the custom scheme is working properly. Go to Safari on your device or in simulator and type in ‘vd://’. This should detect that the app is installed and ask you if you want to open the app.

To test your app via terminal, type in this command:
xcrun simctl openurl booted vd://careers/react-native
Support Universal Links
Please note, for this you’ll need a Developer Account that is fully approved by Apple. Otherwise, you won’t be able to set Associated Domains Capability in XCode.
STEP I - Enable ‘Associated Domains’ in XCode
First, you need to enable “Associated Domains’ in your project settings. To do so, go to your Target’s ‘Signing and Capabilities’ and add a new capability. Use ‘applinks:’ prefix for your domain to tell XCode that this domain is used for Universal Links.

STEP II - Add code in AppDelegate.m
Like before, go to AppDelegate.m and add this code:
- (BOOL)application:(UIApplication *)application continueUserActivity:(nonnull NSUserActivity *)userActivity
restorationHandler:(nonnull void (^)(NSArray<id<UIUserActivityRestoring>> * _Nullable))restorationHandler
{
return [RCTLinkingManager application:application
continueUserActivity:userActivity
restorationHandler:restorationHandler];
}
More:
https://developer.apple.com/documentation/uikit/uiapplicationdelegate/1623072-application
STEP III - Add AASA file on your website
Official docs: https://developer.apple.com/documentation/safariservices/supporting_associated_domains#3381438
Last but not least, you have to make sure that AASA (Apple App Site Association) file is properly uploaded to your server and available under this link (https is important):
https://your-domain.com/.well-known/apple-app-site-association
The simplest structure of this file looks something like this:
{
"applinks": {
"apps": [],
"details": [{
"appID": "<TeamID>.com.vd.mobile",
"paths": ["/careers/*"]
}
]
}
}
Here, the appID is your app’s bundle ID prefixed with your team’s ID. If you log in you can find it on developer.apple.com under Membership. The second property after appID is an array of supported paths. Also, it is possible to add more than one app —for example, if you have multiple bundleIDs per environment. Just add another object to the ‘details’ array.
With this all set up, you should have your Deep Link working as expected.
Android Custom Scheme and App Links
Official docs:
https://developer.android.com/training/app-links
https://developer.android.com/training/app-links/deep-linking
https://developer.android.com/training/app-links/verify-site-associations
To implement Deep Linking in an Android app, you are going to define Intents that are responsible for starting Activities (launching screens). Intent filter is an expression that specifies the type of intents that components can receive and actions that they perform. We want to show the user a specific screen and for this you’ll need to use the ACTION_VIEW intent action.
Go to android/app/src/main/AndroidManifest.xml and add a new intent filter right below the last one in the <activity> tag.
<intent-filter android:label="VentureDevs">
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="vd" />
</intent-filter>
There are also two categories defined. As in official docs:
- BROWSABLE is required in order for the intent filter to be accessible from a web browser. Without it, clicking a link in a browser cannot resolve your app.
- DEFAULT allows your app to respond to implicit intents. Without this, the activity can be started only if the intent specifies your app component name.
You want both of them. The last piece is your custom scheme.
Also, in the same file, make sure that android:launchMode is set to “singleTask”.
<activity
android:name=".MainActivity"
android:launchMode="singleTask">
App Links
There are two steps to enable App Links.
STEP I - Add http/https scheme and host
<intent-filter android:label="VentureDevs" 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="vd" />
<data android:scheme="http" android:host="www.venturedevs.com" />
<data android:scheme="https" />
</intent-filter>
The android:autoVerify="true" will cause a verification of hosts defined in intent filters. It means that Android will check if your website hosts the Digital Asset Links file.
You can define more domains and more intent filters. Just read official docs carefully, which were super helpful for me.
STEP II - Add assetlinks.json file
Again, just like for iOS, make sure that the assetlinks.json file is properly uploaded to your server and available under this link:
https://your-domain.com/.well-known/assetlinks.json.
The file in its most basic form should look like this:
[{
"relation": ["delegate_permission/common.handle_all_urls"],
"target": {
"namespace": "android_app",
"package_name": "you_application_id",
"Sha256_cert_fingerprints": ["14:6D:E9:83:C5:73:06:50:D8:EE:B9:95:2F:34:FC:64:16:A0:83:42:E6:1D:BE:A8:8A:04:96:B2:3F:CF:44:E5"]
}
}]
The SHA256 fingerprints makes up your app’s signing certificate and is stored in your keystore that you use to sign your app.
Test it
To test your app via terminal, type in this command:
$ adb shell am start
-W -a android.intent.action.VIEW
-d "yourscheme://" com.example.android
You can also test Deep Links in Android Studio.
Go to Run -> Edit Configurations. Set the launch options to your scheme, for example vd://careers (assuming that you handle incoming links in React Native and navigate to proper screen).

Handle Links in React Native
Official docs
https://reactnative.dev/docs/linking
Listen for incoming links
After you implement everything on the native side, you can handle incoming links. There are two cases.
App is open
In this case you’ll use Linking.addEventListener(type, handler) where ‘type’ is the type of triggered event, so pass ‘url’ here, and ‘handler’ is a callback to which clicked link is passed and where you can handle it. For example:
Linking.addEventListener(‘url’, (event) => {
Linking.canOpenURL(event.url).then((supported) => {
if (supported) {
yourCustomMethodToHandleUrl(event.url);
}
});
});
Please remember to cancel any listeners you initialize on unmounting, to do this use removeEventListener(type, handler).
App is not open
Link opens it and is passed as `initialURL`, so you are going to use Linking.getInitialURL(url).
Linking.getInitialURL().then((url) => {
if (url) {
Linking.canOpenURL(url).then((supported) => {
if (supported) {
yourCustomMethodToHandleUrl(url);
}
});
}
}).catch((e) => handleErrorSomehow(e));
Handle Links
As you can see I used ‘yourCustomMethodToHandleUrl(url)’ method, this is where you want to:
- Parse and match the link. So you want to extract the path as well any parameters passed. So from ‘https://venturedevs.com/careers&search=react’ you will receive a path ‘careers/’ and a parameter to pass for example as an object { search: react }.
- Navigate to screen. You most probably will have a navigation implementation where you will match the path to proper navigators and pass parameters to desired screen.
Troubleshooting
If you are going to implement a custom scheme, Universal Links and App Links all together, there are a lot of steps to do, so it's not hard to make a mistake. Here is a list of things that might go wrong.
Make sure AASA/applinks.json files are done right
One of the most common mistakes.
For AASA file make sure:
- Your app ID and team ID are correct (especially if you have multiple environments).
- Your file is properly uploaded and available. There should be no redirects and should be served over https://. You can validate your file here: https://branch.io/resources/aasa-validator
- You did not append ‘.json’ to the filename.
- If you make any changes to your AASA file, rebuild your project. iOS is validating it when you first open your app (unlike Android on every launch).
For Android
- Make sure that your intent filter includes all required attributes and action/category elements.
- You can also use a tool for generating applinks.json file https://developers.google.com/digital-asset-links/tools/generator
- You can confirm if the file is properly hosted over API (provide your real domain there): https://digitalassetlinks.googleapis.com/v1/statements:list?source.web.site=https://domain.name:optional_port&relation=delegate_permission/common.handle_all_urls
Rebuild your project
Another common mistake. After you make any changes in native files, rebuild your project.
Expect different behaviors
There is a hidden truth about Deep Links, it’s not going to work the same in all cases, this mostly applies to iOS.
- If you paste your link directly into the browser, it won’t open your application. However, on iOS, it might show you a little baner asking if you want to.
- If you are propet if you want to open the link in the app and you will choose browser instead it might remember your choise.
- On iOS, behavior depends heavily on where you clicked the link. For example if you tap a link in Slack it will work properly, but in Messenger, Facebook or Instagram it might be overridden and the webview will be displayed instead. https://stackoverflow.com/a/37369719
Go through the steps again
If none of the above helped, take a break. Go for a walk. Come back and check all steps one by one, again...
Summary
You should now be able to implement:
- Custom scheme for your app.
- Universal Links on iOS.
- App Links on Android.
That’s a lot. I hope you find this article useful. If you do, please don’t forget to share with other developers.