# How to setup Universal Links for iOS in React Native

# **TL;DR**

This post explains how iOS **Universal Links** work and what is required to implement them. It covers how to handle incoming links in an iOS app, how to create and configure the **Apple App Site Association (AASA) file**, and how to correctly host the file so that iOS can securely associate a domain with an app and route matching URLs from the web into the app.

## **Who should read the post?**

* **Software Developers** who plan to integrate this feature into their app.
    
* **People in tech** with little to no experience on **Universal Links** who want to learn something new.
    

---

# **Introduction**

Have you ever tapped on an ad or email link and been taken directly to the desired application without seeing a website or pop-up asking for permission? That seamless transition from the web to the app isn't magic; it's powered by **Universal Links**.

**Universal Links** allow iOS (or Android) to open your app automatically when a user taps a specific URL belonging to a domain you control. If the app is installed, the link opens the app. If it isn't, the same link simply opens in the browser. This behaviour becomes a powerful tool for ads, emails, deep navigation, onboarding flows, and the overall user experience.

Despite how seamless they feel to users, it requires careful configuration across your iOS app and backend. For developers encountering them for the first time, the setup process can be surprisingly strict and easy to misconfigure.

This post will cover the steps required to make **Universal Links** work on iOS.

* Configure your iOS app to handle incoming links.
    
* Creating the **Apple App Site Association** **(AASA) file**.
    
* Hosting the **AASA file** under the **/.well-known** path.
    

By the end, you will have a better understanding of how to set up an application to support **Universal Links**, which will give you a head start if you plan on integrating this feature into your software.

---

# **Configuring your iOS app to handle incoming links**

The first step is ensuring that your iOS app knows how to react when someone taps a **Universal Link**. Although Universal Links originate on the web, iOS redirects them to the app via the **App Delegate**. This makes the **App Delegate** the central point where your app decides how to handle an **i**ncoming URL. Options include opening a specific screen, triggering navigation, or passing parameters deeper into your **React Native** layer.

When a **Universal Link** is activated, iOS calls two native methods:

* `scene(_:continue:)` for apps using the modern Scenes API
    
* `application(_:continue:restorationHandler:)` for older setups
    

Your job is to implement at least one of these and route the received URL into your code.

Here’s a simplified example using the **SceneDelegate**, which is common in **React Native** projects:

```swift
// SceneDelegate.swift
func scene(_ scene: UIScene, continue userActivity: NSUserActivity) {
    guard userActivity.activityType == NSUserActivityTypeBrowsingWeb,
          let incomingURL = userActivity.webpageURL else { return }

    // Pass the link to React Native
    RCTLinkingManager.application(UIApplication.shared,
                                  open: incomingURL,
                                  options: [:])
}
```

If your project still relies on the **AppDelegate**, the equivalent version looks like:

```typescript
// AppDelegate.m (Objective-C)
- (BOOL)application:(UIApplication *)application
            continueUserActivity:(NSUserActivity *)userActivity
              restorationHandler:(void (^)(NSArray *))restorationHandler
{
  return [RCTLinkingManager application:application
                      continueUserActivity:userActivity
                        restorationHandler:restorationHandler];
}
```

This implementation ensures your **React Nativ**e app receives the link through the Linking API, where you can listen for it:

```typescript
import { Linking } from 'react-native';

Linking.addEventListener('url', ({ url }) => {
  // Handle navigation here
});
```

At this point, the native layer is ready — your app can now receive **Universal Links**.  
Next, we’ll prepare the website side of the handshake by creating the **AASA file** so iOS knows your app is authorised to handle those links.

---

# **Creating the Apple App Site Association (AASA) File**

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1768901210758/cebfa559-aabf-4a15-a8a4-f2bb4fb96bb9.png align="left")

Now that the app can receive **Universal Links**, the next step is to convince iOS that your domain is allowed to open your app. This is where the **AASA file** comes in.

At a high level, the **AASA file** is a JSON document on your website that tells iOS:

* which app(s) belong to your domain
    
* which URL paths those apps are allowed to handle
    
* whether you support features like Shared Web Credentials or **Universal Links**
    

When a user installs your app, iOS automatically fetches the file from your domain. If the file is valid and correctly formatted and located in the expected place, iOS registers your app as the handler for all matching paths.

---

## **AASA File Structure**

Below is the minimal structure iOS expects in an `apple-app-site-association` file and how each section influences link handling:

```json
{
  "applinks": {
    "details": [
      {
        "appIDs": ["ABCDE12345.com.example.app"],
        "paths": ["/products/*", "/profile/*"]
      }
    ]
  }
}
```

The `paths` are obviously the paths from the app that should trigger the **universal link** behaviour.

The **appID** is formed by: `<Application Identifier Prefix>.<Bundle Identifier>`.  
The `Application Identifier Prefix` is visible in the **Apple Developer** portal under Membership or in **Xcode** by choosing your project → Signing & Capabilities → selecting your team. The `Bundle Identifier` comes from the same **Xcode** section and uniquely identifies your app (e.g., [**com.example.app**](http://com.company.app/)).

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1768901785821/c9ed2ad9-77d7-417b-9313-4fcfdad709d5.png align="left")

---

## **Add the Associated Domains Entitlement to Your App**

iOS will only validate the **AASA file** if your app explicitly declares the **domain** **associated** with it. To do this, enable the **Associated Domains** capability in **Xcode** and add an entry such as:

```json
applinks:<your-domain>.com
```

This links your app to the domain so iOS knows where to retrieve and verify your **AASA file.**

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1768908058551/38350c5b-4265-4764-8c9f-20f1717bb84c.png align="left")

---

# **Hosting the AASA File under the** `/.well-known` path.

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1768903303505/7731ffef-b0fb-4ffd-92a4-fb5a5bc4d130.png align="left")

For iOS to recognise your **Universal Links**, the **AASA file** must be publicly accessible at:

```xml
https://your-domain.com/.well-known/apple-app-site-association
```

iOS automatically fetches this file during app installation to verify the domain-app association. Hosting it in any other path or behind redirects will cause verification to fail, so serving it correctly is critical for **Universal Links** to work.

The file must be served directly, without any HTTP redirects (including `301`, `302`, or `307`). iOS will not follow redirects when attempting to fetch the **AASA file**, and even a redirect from `http` to `https` can cause the verification to fail.

Additionally, the response must be served with the correct **Content-Type** header:

```xml
Content-Type: application/json
```

The **AASA file** must be accessible over **HTTPS**, contain valid JSON, and be served as a raw file—**not** wrapped in HTML, compressed, or generated dynamically in a way that alters the response headers.

Because the requirements are strict and vary depending on your hosting provider or **backend setup** (e.g. Nginx, Apache, S3, Firebase Hosting, or a custom server), it’s easy to get this step wrong even if the file itself is correct.

In later posts, we’ll walk through concrete, step-by-step examples of hosting the **AASA file** correctly on different infrastructures and how to debug common issues when iOS fails to recognise your **Universal Links**.

---

# **Conclusion**

Although **Universal Links** can seamlessly transition users from the web to your app, they only work when all the necessary components are correctly configured. Once your app and domain are properly associated, iOS can reliably determine whether to open your app or fall back to the browser when a link is clicked.

This post focuses on the core concepts needed to understand how **Universal Links** work. Follow-up posts will provide concrete setup examples and highlight common mistakes to avoid when implementing them.
