> ## Documentation Index
> Fetch the complete documentation index at: https://dub.co/llms.txt
> Use this file to discover all available pages before exploring further.

# Quickstart

> Learn how to use Dub to create deep links for your mobile app (with native support for React Native, iOS, and Android).

<Note>
  Deep links require a [Pro plan](https://dub.co/pricing) subscription or
  higher.
</Note>

On Dub, you can create deep links that lets you to redirect users to a specific page within your mobile application.

<Frame>
  <img src="https://assets.dub.co/cms/deep-links.jpg" alt="Deep links on Dub" />
</Frame>

For example, you're creating an ad campaign to drive traffic to a specific product page within your mobile app.

By following the quickstart guide below, you'll be able to make sure that all your short links are set up with deep linking functionality.

## Step 1: Configure your deep link domains on Dub

Before you can create deep links, you need to configure your deep link domains in your Dub workspace. This involves adding a custom domain that will be used for your deep links and configuring your deep link configuration files.

### Add a custom domain

First, you'll need to add a custom domain to your Dub workspace. Navigate to your [workspace domain settings](https://app.dub.co/settings/domains) and click **Add Domain**.

You can use a domain you already own, or leverage our [free .link domain offer](/help/article/free-dot-link-domain) to register a custom domain like `yourcompany.link` and use it as your deep link domain.

### Set up your deep link configuration files

Once you've set up your custom domain, you'll need to upload your deep link configuration files to Dub, which we'll host under the `/.well-known/` directory of your domain.

To do that, go to your [workspace domain settings](https://app.dub.co/settings/domains) and click on the edit button for your custom domain:

<Frame>
  <img src="https://assets.dub.co/cms/update-domain-button.png" alt="Deep link configuration files" />
</Frame>

In the domain modal, click on **Show advanced settings**, which will open up the Deep Link Configuration settings fields.

<Frame>
  <img src="https://assets.dub.co/cms/domain-modal-advanced-settings.png" alt="Deep link configuration settings" />
</Frame>

**iOS (apple-app-site-association)**

For iOS apps, upload your [Apple App Site Association file ](https://developer.apple.com/documentation/xcode/supporting-associated-domains#Add-the-associated-domain-file-to-your-website) to enable iOS deep links:

```json apple-app-site-association theme={null}
{
  "applinks": {
    "apps": [],
    "details": [
      {
        "appID": "<YOUR_APPLICATION_IDENTIFIER_PREFIX>.<YOUR_APPLICATION_BUNDLE_IDENTIFIER>",
        "paths": []
      }
    ]
  }
}
```

<Note>
  After updating the AASA file, you may need to reinstall your app since iOS can
  cache the old version of the file, which can lead to inconsistent behavior.
</Note>

**Android (assetlinks.json)**

For Android apps, upload your AssetLinks file to enable Android deep links:

```json assetlinks.json theme={null}
[
  {
    "target": {
      "namespace": "android_app",
      "package_name": "YOUR_PACKAGE_NAME",
      "sha256_cert_fingerprints": []
    },
    "relation": ["delegate_permission/common.handle_all_urls"]
  }
]
```

### Verify that your configuration files are set up correctly

Once you've set up your deep link configuration files, you can go to their respective URLs to verify that they've been configured correctly:

**iOS:** `yourdomain.link/.well-known/apple-app-site-association` [(example)](https://getacme.link/.well-known/apple-app-site-association)

**Android:** `yourdomain.link/.well-known/assetlinks.json` [(example)](https://getacme.link/.well-known/assetlinks.json)

### Allowlist your deep link domain in your app

Last but not least, you'll need to allowlist your deep link domain in your apps to allow them to redirect straight into a page within your app.

**For iOS apps**, you'll need to [allow websites to link to your apps](https://developer.apple.com/documentation/xcode/allowing-apps-and-websites-to-link-to-your-content/).

**For Android apps**, you'll need to [verify your Android app links](https://developer.android.com/training/app-links/verify-android-applinks).

## Step 2: Create your deep links on Dub

Now that you've configured your deep link domains, you can create deep links that will redirect users to specific content within your app.

### Enter your destination URL

Go to your [Dub dashboard](https://app.dub.co) and click **Create Link** in the top navigation bar.

Enter your destination URL in the "Destination URL" field. You can enter the URL with or without the `https://` protocol – behind the scenes, Dub will automatically make sure it's formatted correctly.

This is the URL that opens a specific screen or piece of content within your app. For example `https://yourapp.com/product/Apple-MacBook` opens the product detail screen for `Apple-MacBook`.

<Frame>
  <img src="https://assets.dub.co/help/dub-link-builder.jpg" alt="Deep link builder" />
</Frame>

### Set a short link slug

Under the Short Link field, you can either:

1. Enter your own short link slug
2. Generate a random short link slug
3. Generate a short link slug with AI.

For example, you can set a short link slug like `apple-macbook` to open the product detail screen for `Apple-MacBook`.

<Tip>
  This step is optional. If you don't enter a short link slug, Dub will generate
  a random 7-character slug for you.
</Tip>

### Add device targeting

[Device Targeting](/help/article/device-targeting) enables you to redirect users to the App Store or Google Play Store if your app isn’t installed.

For example, you can set custom destination URLs for different devices using the link builder — use the **iOS Targeting** input for iOS devices, and the **Android Targeting** input for Android devices.

<Frame>
  <img src="https://assets.dub.co/help/device-targeting.png" alt="Deep link device targeting configuration" />
</Frame>

Finally, click **Create link** to create your deep link. This link will act as a Firebase Dynamic Link replacement.

<Note>
  Dub's link builder offers many additional features like UTM builder, password
  protection, expiration dates, geo targeting, and more. [Learn more about
  creating links on Dub](/help/article/how-to-create-link) to explore all
  available options.
</Note>

## Step 3: Handling deep link redirects in your app

When a user opens your app from a deep link, you need to handle two main scenarios:

### Scenario 1: User has your app installed

When your app is already installed, the deep link will open your app directly.
You may handle the deep link manually or with the supported mobile SDKs.

**Option 1**: Handle the deep link using a supported [Dub Mobile SDK](/docs/sdks/client-side-mobile/introduction) (iOS & React Native only)

Follow our installation guide for [Swift](/docs/sdks/client-side-mobile/installation-guides/swift) or [React Native](/docs/sdks/client-side-mobile/installation-guides/react-native) to get started.

<CodeGroup>
  ```typescript React Native expandable theme={null}
  import { useState, useEffect, useRef } from "react";
  import { Linking } from "react-native";
  import AsyncStorage from "@react-native-async-storage/async-storage";
  import dub from "@dub/react-native";

  export default function App() {
    useEffect(() => {
      dub.init({
        publishableKey: "<DUB_PUBLISHABLE_KEY>",
        domain: "<DUB_DOMAIN>",
      });

      // Check if this is first launch
      const isFirstLaunch = await AsyncStorage.getItem("is_first_launch");

      if (isFirstLaunch === null) {
        await handleFirstLaunch();
        await AsyncStorage.setItem("is_first_launch", "false");
      } else {
        // Handle initial deep link url (Android only)
        const url = await Linking.getInitialURL();

        if (url) {
          await handleDeepLink(url);
        }
      }

      const linkingListener = Linking.addEventListener("url", (event) => {
        handleDeepLink(event.url);
      });

      return () => {
        linkingListener.remove();
      };
    }, []);

    const handleFirstLaunch = async (
      deepLinkUrl?: string | null | undefined,
    ): Promise<void> => {
      try {
        const response = await dub.trackOpen(deepLinkUrl);

        const destinationURL = response.link?.url;
        // Navigate to the destination URL
      } catch (error) {
        // Handle error
      }
    };

    // Return your app...
  }
  ```

  ```swift iOS (SwiftUI) expandable theme={null}
  // ContentView.swift
  import SwiftUI
  import Dub

  struct ContentView: View {

      @Environment(\.dub) var dub: Dub

      @AppStorage("is_first_launch") private var isFirstLaunch = true

      var body: some View {
          NavigationStack {
              VStack {
                  // Your app content
              }
              .onOpenURL { url in
                  trackOpen(deepLink: url)
              }
              .onAppear {
                  if isFirstLaunch {
                      trackOpen()
                      isFirstLaunch = false
                  }
              }
          }
      }

      private func trackOpen(deepLink: URL? = nil) {
          Task {
              do {
                  let response = try await dub.trackOpen(deepLink: deepLink)

                  // Obtain the destination URL from the response
                  guard let url = response.link?.url else {
                      return
                  }

                  // Navigate to the destination URL
              } catch let error as DubError {
                  print(error.localizedDescription)
              }
          }
      }
  }
  ```

  ```swift iOS (UIKit) expandable theme={null}
  import UIKit
  import Dub

  @main
  class AppDelegate: UIResponder, UIApplicationDelegate {

      var window: UIWindow?

      private let dubPublishableKey = "<DUB_PUBLISHABLE_KEY>"
      private let dubDomain = "<DUB_DOMAIN>"

      private var isFirstLaunch: Bool {
          get {
              UserDefaults.standard.object(forKey: "is_first_launch") as? Bool ?? true
          }
          set {
              UserDefaults.standard.set(newValue, forKey: "is_first_launch")
          }
      }

      func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
          Dub.setup(publishableKey: dubPublishableKey, domain: dubDomain)

          // Track first launch
          if isFirstLaunch {
              trackOpen()
              isFirstLaunch = false
          }

          // Override point for customization after application launch.
          return true
      }

      func application(_ app: UIApplication, open url: URL, options: [UIApplication.OpenURLOptionsKey : Any] = [:]) -> Bool {
          handleDeepLink(url: url)
          return true
      }

      func handleDeepLink(url: URL) {
          trackOpen(deepLink: url)
      }

      private func trackOpen(deepLink: URL? = nil) {
          // Call the tracking endpoint with the full deep link URL
          Task {
              do {
                  let response = try await Dub.shared.trackOpen(deepLink: deepLink)

                  print(response)

                  // Navigate to final link via link.url
                  guard let destinationUrl = response.link?.url else {
                      return
                  }

                  // Navigate to the destination URL
              } catch let error as DubError {
                  print(error.localizedDescription)
              }
          }
      }
  }
  ```
</CodeGroup>

**Option 2**: Handle the deep link manually

<Steps>
  <Step title="Detect the deep link">
    Your app will receive the deep link URL when it opens. The URL will be in the format: `https://yourdomain.link/short-link-slug`

    <CodeGroup>
      ```javascript React Native theme={null}
      // App.js
      import { useEffect } from "react";
      import { Linking } from "react-native";

      useEffect(() => {
        const handleDeepLink = (url) => {
          try {
            // Call the tracking endpoint with the full deep link URL
            trackDeepLinkClick(url);
          } catch (error) {
            console.error("Error handling deep link URL:", error);
          }
        };

        // Handle deep link when app is already running
        const subscription = Linking.addEventListener("url", (event) => {
          handleDeepLink(event.url);
        });

        // Handle deep link when app is opened from a deep link
        Linking.getInitialURL().then((url) => {
          if (url) {
            handleDeepLink(url);
          }
        });

        return () => {
          subscription?.remove();
        };
      }, []);
      ```

      ```swift iOS (UIKit) theme={null}
      // AppDelegate.swift
      func application(_ app: UIApplication, open url: URL, options: [UIApplication.OpenURLOptionsKey : Any] = [:]) -> Bool {
          handleDeepLink(url: url)
          return true
      }

      func handleDeepLink(url: URL) {
          // Call the tracking endpoint with the full deep link URL
          trackDeepLinkClick(deepLink: url.absoluteString)
      }
      ```

      ```swift iOS (SwiftUI) theme={null}
      // ContentView.swift
      struct ContentView: View {

          var body: some View {
              NavigationStack {
                  VStack {
                    //... your app content
                  }
                  .onOpenURL { url in
                      // Call the tracking endpoint with the full deep link URL
                      trackDeepLinkClick(deepLink: url.absoluteString)
                  }
              }
          }
      }
      ```

      ```kotlin Android theme={null}
      // MainActivity.kt
      override fun onNewIntent(intent: Intent?) {
          super.onNewIntent(intent)
          intent?.data?.let { uri ->
              handleDeepLink(uri)
          }
      }

      private fun handleDeepLink(uri: Uri) {
          // Call the tracking endpoint with the full deep link URL
          trackDeepLinkClick(uri.toString())
      }
      ```
    </CodeGroup>
  </Step>

  <Step title="Track the deep link open">
    Once you’ve detected and parsed the deep link, you should track the open event using Dub's API, which will return the final destination URL in the API response.

    To do this, make a `POST` request to the [`/track/open` endpoint](/docs/api-reference/endpoint/track-open) with the following body:

    ```json theme={null}
    {
      "deepLink": "https://yourdomain.link/short-link-slug"
    }
    ```

    The response will be a JSON object with the following structure if the request is successful:

    ```json theme={null}
    {
      "clickId": "ASltDhoxBiBqKH00",
      "link": {
        "id": "link_1K9XQTLF7OSH3Z48DKQ5WSRVY",
        "domain": "yourdomain.link",
        "key": "short-link-slug",
        "url": "https://yourapp.com/product/Apple-MacBook"
      }
    }
    ```

    Now you've got the destination URL (via `link.url`), you can navigate the user to the correct screen in your app.

    Here's the full example code for React Native, iOS, and Android.

    <CodeGroup>
      ```javascript React Native theme={null}
      // DeepLinkTracker.js
      async function trackDeepLinkClick(deepLink) {
        try {
          const response = await fetch(`https://api.dub.co/track/open`, {
            method: "POST",
            headers: {
              "Content-Type": "application/json",
            },
            body: JSON.stringify({
              deepLink,
            }),
          });

          if (response.ok) {
            const data = await response.json();
            const destinationUrl = data.link.url;

            // Navigate to the destination URL in your app
            navigateToDestination(destinationUrl);
          }
        } catch (error) {
          console.error("Error tracking deep link:", error);
        }
      }
      ```

      ```swift iOS theme={null}
      // DeepLinkTracker.swift
      func trackDeepLinkClick(deepLink: String) {
          let url = URL(string: "https://api.dub.co/track/open")!
          var request = URLRequest(url: url)
          request.httpMethod = "POST"
          request.setValue("application/json", forHTTPHeaderField: "Content-Type")

          let body = [
              "deepLink": deepLink,
          ]

          request.httpBody = try? JSONSerialization.data(withJSONObject: body)

          URLSession.shared.dataTask(with: request) { data, response, error in
              if let data = data,
                 let json = try? JSONSerialization.jsonObject(with: data) as? [String: Any],
                 let link = json["link"] as? [String: Any],
                 let destinationUrl = link["url"] as? String {
                  DispatchQueue.main.async {
                      self.navigateToDestination(destinationUrl)
                  }
              }
          }.resume()
      }
      ```

      ```kotlin Android theme={null}
      // DeepLinkTracker.kt
      private fun trackDeepLinkClick(deepLink: String) {
          val url = URL("https://api.dub.co/track/open")
          val connection = url.openConnection() as HttpURLConnection

          connection.requestMethod = "POST"
          connection.setRequestProperty("Content-Type", "application/json")
          connection.doOutput = true

          val body = JSONObject().apply {
              put("deepLink", deepLink)
          }

          connection.outputStream.use { os ->
              os.write(body.toString().toByteArray())
          }

          val response = connection.inputStream.bufferedReader().use { it.readText() }
          val jsonResponse = JSONObject(response)
          val link = jsonResponse.getJSONObject("link")
          val destinationUrl = link.getString("url")

          runOnUiThread {
              navigateToDestination(destinationUrl)
          }
      }
      ```
    </CodeGroup>
  </Step>
</Steps>

### Scenario 2: User doesn't have your app installed

When your app isn't installed, the user will be redirected to the App Store or Google Play Store since you've enabled [device targeting](/help/article/device-targeting) in Step 2 above.

After they install and open your app, you'll need to use [deferred deep linking](/docs/concepts/deep-links/deferred-deep-linking) to:

1. Retrieve the short link that brought the user to the app store
2. Track the open event via the [`/track/open` endpoint](/docs/api-reference/endpoint/track-open)
3. Redirect the user to the final destination URL

<Note>
  For detailed implementation of deferred deep linking, including how to use the
  Google Play Store Install Referrer API and other services, see our [Deferred
  Deep Linking](/docs/concepts/deep-links/deferred-deep-linking) guide.
</Note>

## Testing deep links

Before deploying your deep links to production, test them thoroughly using emulators to ensure they work correctly.

<Steps>
  <Step title="Test on Android emulator">
    Use the Android Debug Bridge (adb) to test your deep links on an Android emulator:

    ```bash theme={null}
    adb shell am start -W -a android.intent.action.VIEW -d "https://yourdomain.link/your-short-link" com.yourpackage.name
    ```
  </Step>

  <Step title="Test on iOS simulator">
    Use the `xcrun` command to test your deep links on an iOS simulator:

    ```bash theme={null}
    xcrun simctl openurl booted "https://yourdomain.link/your-short-link"
    ```
  </Step>

  <Step title="Test on your device">
    You can also test your deep links by opening them from a messaging app on your device:

    1. Open a messaging app (like Messages, WhatsApp, or Telegram) on your device
    2. Send yourself a message containing your deep link URL
    3. Tap on the link in the message
    4. The link should either open your app directly or redirect to the appropriate app store
  </Step>
</Steps>

## Troubleshooting deep links

If your deep links aren't working as expected, here are some common issues and solutions:

<Steps>
  <Step title="Deep link domain not allowlisted">
    Make sure you've [allowlisted your deep link domain](/docs/concepts/deep-links/quickstart#allowlist-your-deep-link-domain-in-your-app) in your app's configuration. This is required for both iOS and Android to recognize and handle links from your domain.
  </Step>

  <Step title="iOS/Android cached outdated deep link configuration">
    If you've completed all steps above and your deep links still aren't working, it's likely because iOS or Android cached the outdated deep link configuration.

    Try uninstalling and reinstalling your app to clear the cache. This forces the operating system to re-fetch the `apple-app-site-association` or `assetlinks.json` files.
  </Step>

  <Step title="Deep link wrapped by a third-party links">
    If your links are being shared through marketing platforms, email providers, or click trackers, the original URL may be getting wrapped or modified.

    When a third-party service wraps your Dub link in their own tracking URL, the deep link won't work because the device sees the wrapper domain instead of your allowlisted domain.

    Contact your marketing or analytics provider to ensure Dub links are passed through without modification.
  </Step>

  <Step title="iOS URL handler short-circuited by third-party SDKs">
    If you're using third-party SDKs (e.g., Facebook/Meta SDK, Google Sign-In, or other authentication SDKs) alongside Dub in an iOS app, your `openURL` handler may silently fail to trigger Dub's deep link tracking.

    This happens when multiple URL handlers are chained with a short-circuit `||` (OR) operator in your `AppDelegate`. If a third-party SDK processes the URL first and returns `true`, subsequent handlers never execute — meaning `RCTLinkingManager` (React Native) or Dub's `handleDeepLink` / `trackOpen` never gets called.

    This is especially common when links are opened from in-app browsers (e.g., Facebook or Instagram's in-app browser).

    **How to fix this:**

    Replace the short-circuit `||` with a non-short-circuit `|` operator (or call all handlers independently) to ensure every SDK gets a chance to process the URL:

    <CodeGroup>
      ```objc AppDelegate.mm (React Native)  theme={null}
      // ❌ Bad: Short-circuit OR — if FBSDKApplicationDelegate returns YES, 
      // RCTLinkingManager never fires
      - (BOOL)application:(UIApplication *)app
                  openURL:(NSURL *)url
                  options:(NSDictionary<UIApplicationOpenURLOptionsKey, id> *)options {
        return [[FBSDKApplicationDelegate sharedInstance] application:app openURL:url options:options]
            || [RCTLinkingManager application:app openURL:url options:options];
      }

      // ✅ Good: Non-short-circuit OR — both handlers always run
      - (BOOL)application:(UIApplication *)app
                  openURL:(NSURL *)url
                  options:(NSDictionary<UIApplicationOpenURLOptionsKey, id> *)options {
        BOOL fbHandled = [[FBSDKApplicationDelegate sharedInstance] application:app openURL:url options:options];
        BOOL rnHandled = [RCTLinkingManager application:app openURL:url options:options];
        return fbHandled || rnHandled;
      }
      ```

      ```swift AppDelegate.swift (Swift) theme={null}
      // ❌ Bad: Short-circuit OR — if the Facebook SDK returns true,
      // handleDeepLink never fires
      func application(_ app: UIApplication, open url: URL,
                       options: [UIApplication.OpenURLOptionsKey: Any] = [:]) -> Bool {
          return ApplicationDelegate.shared.application(app, open: url, options: options)
              || handleDeepLink(url: url)
      }

      // ✅ Good: Call each handler independently
      func application(_ app: UIApplication, open url: URL,
                       options: [UIApplication.OpenURLOptionsKey: Any] = [:]) -> Bool {
          let fbHandled = ApplicationDelegate.shared.application(app, open: url, options: options)
          let dubHandled = handleDeepLink(url: url)
          return fbHandled || dubHandled
      }
      ```
    </CodeGroup>

    <Warning>
      This issue is easy to miss because deep links may still work in most
      scenarios — it only fails when the URL is first processed by another SDK
      (e.g., when a user opens your link from Facebook or Instagram's in-app
      browser). Always test deep links from third-party apps where your SDKs are
      active.
    </Warning>
  </Step>
</Steps>
