Eugene Hauptmann Eugene is a faith-centric technologist, a serial entrepreneur, angel investor, advisor, and mentor. He is the founder and CEO of REACTIVE LIONS INC. Find him on LinkedIn.

Understanding deep linking in React Native

7 min read 1971


Deep linking and universal links are the gateways into your application. Deep linking is already part of a seamless experience that any mobile application should have.

Ultimately, they help to reduce churn and increase loyalty of your user base. Implementing them correctly will have a direct impact on your ability to master campaigns and run promotions within your application.

The deep linking question is as important today as ever before, specifically taking into consideration Identifier for Advertisers (IDFA) and a rising number of walled gardens. Well-executed deep linking will enable your retargeting campaigns and bring engagement to a new level, allowing end users to have seamless one-click experience between the web and your application.

Once users discover your application and install it, deep linking becomes a perfect tool to retain newly acquired users in your app.

In this article, I outline existing ways on how to implement deep linking and how to test it using React Native Typescript codebase.

You can find the full source code for this project available on GitHub.

What is deep linking and why is it important?

Deep linking, in a nutshell, is a way to redirect users from a webpage into your application in order to show a specific screen with a requested content. It can be a product, an article, secure content behind a paywall, or a login screen.

One of the most famous examples is the link to Slack that they send to your email, which gets opened right inside the application, authorizing you to use it with your account — no password needed.

Screenshot of Slack magic link

Deep linking is paramount in 2021. Every effort to lead your users into the app and improve their engagement will heavily depend on the strategy based on top of the deep linking.

We made a custom demo for .
No really. Click here to check it out.

To summarize the main points why deep linking is important:

  • Marketing campaigns
  • User retention
  • Seamless redirects between web and mobile
  • Content delivery behind a paywall or login authentication
  • Increasing customer lifecycle
  • Improving loyalty
  • Minimizing churn
  • Improved search engine ranking

Implementing deep linking requires a more intricate understanding of iOS and Android for extra configuration of each platform in your React Native project.

Take, for example, this syntax diagram of the following URL:


Diagram of URL scheme
Whenever you navigate to a website using, for example,, you use a URL in which the URL scheme is “https”. In the example above, billing-app is a URL scheme for your deep linking URL.

Deep linking and universal linking in iOS

Starting with iOS 9, Apple introduced universal links to reduce confusion and simplify the user experience.

The idea behind using universal links is to connect specific website URLs that match content on your website with content inside your application. This URL would act the same way as the deep linking URL I have shown in the previous section:

Configuring universal links requires extra steps on both the server side and mobile side.

First you start with the server side, where you need to upload a JSON formatted file that defines association of the website with a mobile application and its specific routes.

Let’s say you run the domain and you want to create an association file. Start by creating a folder or a route in your root domain .well-known, then add JSON content inside the apple-app-site-association:

Add JSON content to define website associations:

   "applinks": {
       "apps": [],
       "details": [
               "appID": "",
               "paths": [ "/billing/", "/billing/*"]
               "appID": "",
               "paths": [ "*" ]

How to configure your Xcode project

To demonstrate how deep linking works, we are going to build a simple test application. This application will have straightforward navigation between the Home and Billing screens using the @react-navigation component:

npx react-native init BillingApp --template

Open your Xcode workspace:

open BillingApp/ios/BillingApp.xcworkspace

Screenshot of Xcode workspace
In your Xcode window, select your newly created project in the left pane (in our case it’s BillingApp). Next, select the BillingApp target inside the newly opened left pane of the internal view for the BillingApp.xcodeproj.

Navigate to the Info section in the top center of that view, then go to the very bottom and click the plus (+) sign under URL Types. Make sure to add billing-id as your new Identifier and specify URL Schemes as billing-app.

By following these steps above, you’ve enabled iOS project configuration to use deep links like billing-app://billing/4 inside your Objective C and JavaScript code later on.

After configuring Xcode, the next step will be focused on React Native. I will start with linking part of the React Native core called LinkingIOS. You can read more about it in the official documentation here.

Its main goal is to construct a bridge that will enable a JavaScript thread to receive updates from the native part of your application, which you can read more about in the AppDelegate.m part below.

Go to ios/Podfile and add this line under target:

pod 'React-RCTLinking', :path => '../node_modules/react-native/Libraries/LinkingIOS'

And then make sure to update your pods using this command:

cd ios && pod install

The next step is to enable the main entry points of your application to have control over the callbacks that are being called when the application gets opened via deep linking.

In this case we implement the function openURL with options and pass its context to RCTLinkingManager via its native module called RCTLinkingManager.

#import <React/RCTLinkingManager.h>

- (BOOL)application:(UIApplication *)application
openURL:(NSURL *)url
options:(NSDictionary<UIApplicationOpenURLOptionsKey,id> *)options
return [RCTLinkingManager application:application openURL:url options:options];

For the universal links we will need to implement a callback function continueUserActivity, which will also pass in context of the app and current universal link into the JavaScript context via RCTLinkingManager.

- (BOOL)application:(UIApplication *)application continueUserActivity:(nonnull NSUserActivity *)userActivity
restorationHandler:(nonnull void (^)(NSArray<id<UIUserActivityRestoring>> * _Nullable))restorationHandler
return [RCTLinkingManager application:application

Deep linking in Android

Android deep linking works slightly differently in comparison to iOS. This configuration operates on top of Android Intents, an abstraction of an operation to be performed. Most of the configuration is stored under AndroidManifest.xml and works by actually pointing to which Intent will be opened when the deep link is executed.

How to configure your Android Studio project

Inside your Android manifest android/app/src/main/AndroidManifest.xml we need to do the following:

  • Configure the Intent filter
  • Define the main View action and specify two main categories: DEFAULT and BROWSABLE
  • Finalize the configuration by setting the scheme to billing-app and defining the main route as billing

This way Android will know that this app has deep linking configured for this route billing-app://billing/*:

<intent-filter android:label="filter_react_native">
 <action android:name="android.intent.action.VIEW" />
 <category android:name="android.intent.category.DEFAULT" />
 <category android:name="android.intent.category.BROWSABLE" />
 <data android:scheme="billing-app" android:host="billing" />

Navigation and deep linking

In most production-grade applications you’ll end up having multiple screens, and you are most likely to end up using some form of component that implements this navigation for you. However, you can opt-out and use deep linking without navigation context by invoking React Native’s core library via JavaScript by calling Linking directly.

You can do this inside your React Native code using these two methods:

  1. If the app is already open:
    Linking.addEventListener('url', ({url}) => {})
  2. If the application is not already open and you want to get the initial URL, use this call:

Use the acquired deep linking URL to show different content, based on the logic of your application.

If you’re using @react-navigation you can opt-in to configure deep linking using its routing logic.

For this you need to define your prefixes for both universal linking and deep linking. You will also need to define config with its screens, including nested screens if your application has many screens and is very complex.

Here’s an example of how this configuration looks for our application:

import { NavigationContainer } from '@react-navigation/native';
export const config = {
 screens: {
   Home: {
     path: 'home/:id?',
     parse: {
       id: (id: String) => `${id}`,
   Billing: {
     path: 'billing/:id?',
     parse: {
       id: (id: String) => `${id}`,
const linking = {
 prefixes: ['', 'billing-app://home'],
function App() {
 return (
   <NavigationContainer linking={linking} fallback={<Text>Loading...</Text>}>
     {/* content */}

In the code section above, we introduced universal linking and walked through the steps needed to define universal link association on your website’s server end. In Android there’s something similar called Verified Android App Links.

Using Android App Links helps you avoid the confusion of opening deep links with other applications that aren’t yours. Android usually suggests using a browser to open unverified deep links whenever it is unsure if they are App Links (and not deep links).

To enable App Links verification, you will need to change intent declaration in your manifest file like so:

<intent-filter android:autoVerify="true">

To create app-verified links you will need to generate a JSON verification file that will be placed in the same .well-known folder as in the Xcode section:

keytool -list -v -keystore my-release-key.keystore

This command will generate association with your domain by signing the configuration with your keystore file:

  "relation": ["delegate_permission/common.handle_all_urls"],
  "target": {
    "namespace": "android_app",
    "package_name": "com.mycompany.app1",

Then place the generated file to your website using this path:

How to test deep links

After going through all configurations and implementations, you want to ensure you’ve set everything up correctly and that deep links work on each platform of your choice.

Before you test universal links or Android App Verified Links, make sure that all JSON files are uploaded, available, and up to date for each of your domains. Depending on your web infrastructure, you might even want to refresh your Content Delivery Network (CDN) cache.

A successful deep linking test means that, after opening a deep link in the browser, you are forwarded to your application and you can see the desired screen with the given content.

When you go to the billing screen you can specify a number, and the application will render the same number of emojis with flying dollar banknotes. Our application has Home and Billing screens.

If you try to go to the Billing screen from your Home screen, it won’t pass any content, and therefore it will not render any emojis.

In your terminal, you can use these commands to test deep linking for each platform. Play around by changing the number at the end of your deep linking URL to see different numbers of emojis.

  1. iOS
    npx uri-scheme open billing-app://billing/5 --ios

    You can also open Safari and enter billing-app://billing/5 in your address bar, then click go.

    Screenshot of billing app in iOS with dollar sign emojis

  2. Android
    npx uri-scheme open billing-app://billing/5 --android

    Screenshot of billing app in Android with dollar sign emojis

Next steps

You might have noticed that I used TypeScript to write the code for this project. For this project I’ve implemented custom property types that require custom declaration for each screen. Check props.ts to see these type declarations.

As I mentioned earlier, if you’re building a production-grade application, you’re most likely to end up building complex routing and will need nesting routes to be implemented with your navigator library.

Nesting navigation will enable you to decompose each screen to smaller components and have sub routes based on your business logic. Learn more about building nesting routes using @react-navigation here.

Looking forward to seeing what you build with this!

: Full visibility into your web apps

LogRocket is a frontend application monitoring solution that lets you replay problems as if they happened in your own browser. Instead of guessing why errors happen, or asking users for screenshots and log dumps, LogRocket lets you replay the session to quickly understand what went wrong. It works perfectly with any app, regardless of framework, and has plugins to log additional context from Redux, Vuex, and @ngrx/store.

In addition to logging Redux actions and state, LogRocket records console logs, JavaScript errors, stacktraces, network requests/responses with headers + bodies, browser metadata, and custom logs. It also instruments the DOM to record the HTML and CSS on the page, recreating pixel-perfect videos of even the most complex single-page apps.

Eugene Hauptmann Eugene is a faith-centric technologist, a serial entrepreneur, angel investor, advisor, and mentor. He is the founder and CEO of REACTIVE LIONS INC. Find him on LinkedIn.

Leave a Reply