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

# Deep Linking Setup

> Complete guide to setting up and verifying deep links with Linkrunner for React Native and Flutter apps

Deep links allow users to navigate directly to specific content within your app by clicking on a URL. This guide walks you through the complete setup — from creating your verification config files and saving them in Linkrunner, to making the necessary code changes in your app.

There are two primary approaches to deep linking:

1. **[HTTP/HTTPS Deep Links](#httphttps-deep-linking)**: URLs with `http://` or `https://` protocols that can open your app when clicked. Requires domain verification.
2. **[Custom URI Schemes](#custom-uri-schemes)**: URLs with a custom protocol like `myapp://` that are registered to your app. No verification needed.

***

## HTTP/HTTPS Deep Linking

HTTP/HTTPS deep links (including App Links on Android and Universal Links on iOS) require you to prove domain ownership before they work reliably. The setup has four parts:

1. Create your verification config files
2. Save them in Linkrunner
3. Update native configuration (Android & iOS)
4. Configure your app's navigation

### Step 1: Create Verification Config Files

<Tabs>
  <Tab title="Android">
    #### Create the Digital Asset Links file

    Create a file named `assetlinks.json` with the following content:

    ```json theme={null}
    [
        {
            "relation": ["delegate_permission/common.handle_all_urls"],
            "target": {
                "namespace": "android_app",
                "package_name": "your.package.name",
                "sha256_cert_fingerprints": ["SHA-256:XX:XX:XX:..."]
            }
        }
    ]
    ```

    Replace:

    * `your.package.name` with your actual Android package name
    * `SHA-256:XX:XX:XX:...` with your app's SHA-256 fingerprint

    #### Get your app's SHA-256 fingerprint

    For debug builds:

    ```bash theme={null}
    keytool -list -v -keystore ~/.android/debug.keystore -alias androiddebugkey -storepass android -keypass android
    ```

    For release builds:

    ```bash theme={null}
    keytool -list -v -keystore your_release_keystore.keystore -alias your_key_alias
    ```

    Look for the "SHA-256 Certificate fingerprint" line in the output.

    <Warning>
      Debug and release builds are signed with different keystores, so they produce different SHA-256 fingerprints. App Links verification will only succeed for the build whose fingerprint is currently saved in Linkrunner.

      Before testing or shipping a release build, either:

      * Update the `sha256_cert_fingerprints` value in Linkrunner to your release keystore's fingerprint, or
      * List both fingerprints in the array so debug and release builds both verify:

      ```json theme={null}
      "sha256_cert_fingerprints": [
          "SHA-256:DEBUG:FINGERPRINT:...",
          "SHA-256:RELEASE:FINGERPRINT:..."
      ]
      ```

      If you use Google Play App Signing, get the release fingerprint from **Play Console → Setup → App integrity**, not your local upload keystore.
    </Warning>
  </Tab>

  <Tab title="iOS">
    #### Create the Apple App Site Association file

    Create a file named `apple-app-site-association` (no file extension) with the following content:

    ```json theme={null}
    {
        "applinks": {
            "apps": [],
            "details": [
                {
                    "appID": "TEAM_ID.BUNDLE_ID",
                    "paths": ["/*"]
                }
            ]
        }
    }
    ```

    Replace:

    * `TEAM_ID` with your Apple Developer Team ID (found in the [Apple Developer Portal](https://developer.apple.com/account) under Membership Details)
    * `BUNDLE_ID` with your app's bundle identifier (found in your Xcode project settings)

    The `paths` array can be customized to include only specific paths your app should handle. Use `/*` to handle all paths.
  </Tab>
</Tabs>

### Step 2: Save Verification Config in Linkrunner

Linkrunner hosts your verification files automatically so you don't have to manage server configuration yourself.

1. Log in to your [Linkrunner dashboard](https://dashboard.linkrunner.io/settings?sort_by=activity-1\&s=store-verification)
2. Go to **Project Settings** from the navigation menu
3. In the **Domain Verification** section:
   * Paste your `apple-app-site-association` JSON in the **iOS (Only JSON allowed)** text area
   * Paste your `assetlinks.json` content in the **Android (Only JSON allowed)** text area
4. Click **Save**

Linkrunner will automatically host these files at:

* iOS: `https://your-domain.io/.well-known/apple-app-site-association`
* Android: `https://your-domain.io/.well-known/assetlinks.json`

### Step 3: Update Native Configuration

These changes are the same whether you're using React Native or Flutter.

<Tabs>
  <Tab title="Android">
    Open `android/app/src/main/AndroidManifest.xml` and add the following inside the `<activity>` section:

    ```xml theme={null}
    <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" />
      <!-- Your domain and subdomains -->
      <data android:scheme="https" android:host="example.com" />
      <data android:scheme="https" android:host="app.example.com" />
      <data android:scheme="https" android:host="store.example.com" />
    </intent-filter>
    ```
  </Tab>

  <Tab title="iOS">
    1. Open your iOS project in Xcode
    2. Go to **Signing & Capabilities**
    3. Add the **Associated Domains** capability
    4. Add your domains:

    ```
    applinks:example.com
    applinks:app.example.com
    applinks:store.example.com
    ```

    For more details, see the [official Apple documentation](https://developer.apple.com/documentation/xcode/supporting-associated-domains).
  </Tab>
</Tabs>

### Step 4: Configure Navigation

<Tabs>
  <Tab title="React Native">
    React Native uses [React Navigation](https://reactnavigation.org/) for handling deep links.

    ```javascript theme={null}
    // App.js or your navigation configuration file
    import { NavigationContainer } from "@react-navigation/native";
    import { createStackNavigator } from "@react-navigation/stack";
    import LinkRunner from "@linkrunner/react-native";

    const Stack = createStackNavigator();

    function App() {
        const linking = {
            prefixes: [
                "https://example.com",
                "https://app.example.com",
                "https://store.example.com",
            ],
            config: {
                screens: {
                    Home: "",
                    Profile: "profile/:id",
                    Store: {
                        path: "store/:category?",
                        parse: {
                            category: (category) => category || "all",
                        },
                    },
                    "app.example.com": {
                        screens: {
                            AppSpecificScreen: ":id",
                        },
                    },
                    "store.example.com": {
                        screens: {
                            StoreSpecificScreen: ":id",
                        },
                    },
                },
            },
        };

        return (
            <NavigationContainer linking={linking}>
                <Stack.Navigator>{/* Your screens */}</Stack.Navigator>
            </NavigationContainer>
        );
    }

    export default App;
    ```
  </Tab>

  <Tab title="Flutter">
    Flutter uses [go\_router](https://pub.dev/packages/go_router) or its own navigation system to handle deep links.

    ```dart theme={null}
    import 'package:flutter/material.dart';
    import 'package:go_router/go_router.dart';
    import 'package:linkrunner/linkrunner.dart';

    void main() {
      runApp(MyApp());
    }

    class MyApp extends StatelessWidget {
      MyApp({Key? key}) : super(key: key);

      final GoRouter _router = GoRouter(
        initialLocation: '/',
        routes: [
          GoRoute(
            path: '/',
            builder: (context, state) => HomeScreen(),
          ),
          GoRoute(
            path: '/profile/:id',
            builder: (context, state) {
              final id = state.params['id']!;
              return ProfileScreen(id: id);
            },
          ),
          GoRoute(
            path: '/app/:id',
            builder: (context, state) {
              final id = state.params['id']!;
              return AppSpecificScreen(id: id);
            },
          ),
          GoRoute(
            path: '/store/:category',
            builder: (context, state) {
              final category = state.params['category'] ?? 'all';
              return StoreScreen(category: category);
            },
          ),
        ],
        redirect: (context, state) {
          final uri = Uri.parse(state.location);
          if (uri.host == 'app.example.com') {
            return '/app/${uri.pathSegments.isNotEmpty ? uri.pathSegments.first : ''}';
          } else if (uri.host == 'store.example.com') {
            return '/store/${uri.pathSegments.isNotEmpty ? uri.pathSegments.first : 'all'}';
          }
          return null;
        },
      );

      @override
      Widget build(BuildContext context) {
        return MaterialApp.router(
          routerConfig: _router,
          title: 'My App',
        );
      }
    }
    ```
  </Tab>
</Tabs>

***

## Custom URI Schemes

Custom URI schemes use a custom protocol like `myapp://` and don't require domain verification. They're useful for backward compatibility or simpler setups.

### Native Configuration

<Tabs>
  <Tab title="Android">
    Open `android/app/src/main/AndroidManifest.xml` and add inside the `<activity>` section:

    ```xml theme={null}
    <intent-filter>
      <action android:name="android.intent.action.VIEW" />
      <category android:name="android.intent.category.DEFAULT" />
      <category android:name="android.intent.category.BROWSABLE" />
      <data android:scheme="myapp" />
    </intent-filter>
    ```
  </Tab>

  <Tab title="iOS">
    1. Open your iOS project in Xcode
    2. Go to the **Info** tab
    3. Add a new entry to **URL Types** with:
       * Identifier: Your app bundle identifier (e.g., `com.example.myapp`)
       * URL Schemes: Your custom scheme (e.g., `myapp`)

    In `Info.plist`:

    ```xml theme={null}
    <key>CFBundleURLTypes</key>
    <array>
      <dict>
        <key>CFBundleTypeRole</key>
        <string>Editor</string>
        <key>CFBundleURLName</key>
        <string>com.example.myapp</string>
        <key>CFBundleURLSchemes</key>
        <array>
          <string>myapp</string>
        </array>
      </dict>
    </array>
    ```
  </Tab>
</Tabs>

***

## Testing Your Deep Links

<Tabs>
  <Tab title="Android">
    Use `adb` to test:

    ```bash theme={null}
    # Test HTTP/HTTPS deep links
    adb shell am start -a android.intent.action.VIEW -d "https://app.example.com/profile/123" your.package.name

    # Test custom URI scheme
    adb shell am start -a android.intent.action.VIEW -d "myapp://profile/123" your.package.name
    ```

    ### Troubleshooting

    * **App doesn't open automatically**: Ensure your `assetlinks.json` is correctly formatted, the SHA-256 fingerprint matches your signing key, and the file is accessible via HTTPS.
    * **Verification warnings**: Look for "IntentFilterIntentSvc" messages in logcat for details on verification failures.
  </Tab>

  <Tab title="iOS">
    Use the iOS Simulator:

    ```bash theme={null}
    # Test HTTP/HTTPS deep links
    xcrun simctl openurl booted "https://app.example.com/profile/123"

    # Test custom URI scheme
    xcrun simctl openurl booted "myapp://profile/123"
    ```

    ### Troubleshooting

    * **App doesn't open with Universal Links**: Ensure your `apple-app-site-association` file is correctly formatted, Team ID and Bundle ID are correct, the file is accessible via HTTPS, and Associated Domains capability is enabled.
    * **Debug Universal Links**: Connect your device to a Mac, open Console.app and filter for "swcd" to see Universal Links logs.
  </Tab>

  <Tab title="Web">
    Create actual links on your website and test on real devices:

    * For HTTP/HTTPS links: `https://app.example.com/profile/123`
    * For custom URI schemes: `myapp://profile/123`
  </Tab>
</Tabs>
