Deep Linking in FlutterFlow: A Complete Guide

Supabase and Firebase Auth
Supabase Notifications and Auth
by John Kealy
Published on September 16th, 2025

Deep linking is conceptually very simple.

You receive a link on the web, say, for example, because someone has used the share button in a mobile application to generate it, and when you tap it, the app opens on your phone and it navigates to the correct page using the correct parameters.

But this very simple concept became incredibly overcomplicated when FlutterFlow's existing solution, which was Firebase Dynamic Links, was retired. Now in the FlutterFlow community, you'll hear a lot of references to the new, sort-of "official-ish" replacement for Dynamic Links, which is to use BranchIO. But the community generally found that BranchIO was proving to be confusing, brittle, and seemed to be prone to hidden costs.

Ultimately however, the reason that Firebase Dynamic Links was retired was because the tooling had advanced in the space so much that actually, deep links these days are pretty straightforward. In this article, I'll give you the no-frills, simple, and free way to implement deep links in your FlutterFlow app – without Branch.io or any other third-party service.

Flutter to the rescue

The first port of call is to visit this page:

https://docs.flutter.dev/ui/navigation/deep-linking

This article will more or less follow this guide, but with a FlutterFlow focus and a practical means of deploying the necessary code.

Hosting the Configuration files

The way that deep links work is that the link resolves to a web page online, and that webpage contains configuration files that the deep links know how to interpret. Since you're probably already using Firebase (say, for authentication, or push notifications) in your FlutterFlow app, Firebase hosting is the simplest way to go. However, you can host these files for free with any web hosting server you like, such as Netlify or Vercel.

Open a terminal, ask your favourite AI to install the firebase CLI for you, and login.

Choose where this website will live, be in on your local machine or in a GitHub repo, and then run

> firebase init

This will create a few configuration files and a public/ folder, with an index.html file inside it.

Simply smash the following into an AI and ask it to fill in the placeholders with details about your own app, then replace the contents of index.html with it. You can also host social sharing images if you like – simply ask the AI how to add them.


<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <title>Your App Name - Redirecting...</title>

    <!-- SEO Meta Tags -->
    <meta name="description" content="Your app description here - explain what your app does">

    <!-- Open Graph Meta Tags (used by Facebook, LinkedIn, WhatsApp, and fallback for Twitter/X) -->
    <meta property="og:type" content="website">
    <meta property="og:title" content="Your App Name - Your App Tagline">
    <meta property="og:description" content="Your app description here - explain what your app does in more detail">
    <meta property="og:image" content="https://your-project-id.web.app/og-image.png">
    <meta property="og:image:width" content="1200">
    <meta property="og:image:height" content="630">
    <meta property="og:url" content="https://your-project-id.web.app">
    <meta property="og:site_name" content="Your App Name">

    <!-- Twitter/X Card Meta Tags (minimal - uses OG tags as fallback) -->
    <meta name="twitter:card" content="summary_large_image">
    <meta name="twitter:site" content="@yourtwitterhandle">

    <style>
      body {
        background: #f5f5f5;
        color: #333;
        font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif;
        margin: 0;
        padding: 0;
        display: flex;
        justify-content: center;
        align-items: center;
        min-height: 100vh;
      }

      .container {
        text-align: center;
        padding: 20px;
      }

      h1 {
        font-size: 24px;
        margin-bottom: 10px;
        font-weight: 600;
      }

      p {
        font-size: 16px;
        color: #666;
        margin-bottom: 20px;
      }

      .spinner {
        border: 3px solid #f3f3f3;
        border-top: 3px solid #3498db;
        border-radius: 50%;
        width: 40px;
        height: 40px;
        animation: spin 1s linear infinite;
        margin: 20px auto;
      }

      @keyframes spin {
        0% { transform: rotate(0deg); }
        100% { transform: rotate(360deg); }
      }

      .manual-link {
        margin-top: 30px;
        font-size: 14px;
        color: #999;
      }

      .manual-link a {
        color: #3498db;
        text-decoration: none;
      }

      .manual-link a:hover {
        text-decoration: underline;
      }
    </style>
  </head>
  <body>
    <div class="container">
      <h1>Your App Name</h1>
      <p>Redirecting you to the right place...</p>
      <div class="spinner"></div>
      <div class="manual-link">
        Not redirecting? <a href="#" id="manual-redirect">Click here</a>
      </div>
    </div>

    <script>
      (function() {
        // Get the current path for deep linking
        const currentPath = window.location.pathname + window.location.search + window.location.hash;

        // Deep link schemes
        const APP_SCHEME = 'yourappscheme://'; // TODO: Replace with your actual app scheme
        const IOS_UNIVERSAL_LINK = 'https://your-project-id.web.app'; // Your universal link domain

        // Store URLs
        const APP_STORE_URL = 'https://apps.apple.com/us/app/your-app-name/id[YOUR_APP_ID]'; // TODO: Replace with actual App Store ID
        const PLAY_STORE_URL = 'https://play.google.com/store/apps/details?id=com.yourcompany.yourapp'; // TODO: Replace with your app package name
        const WEBSITE_URL = 'https://your-website.com/'; // TODO: Replace with your website URL

        function detectPlatform() {
          const userAgent = navigator.userAgent || navigator.vendor || window.opera;

          // iOS detection
          if (/iPad|iPhone|iPod/.test(userAgent) && !window.MSStream) {
            return 'ios';
          }

          // Android detection
          if (/android/i.test(userAgent)) {
            return 'android';
          }

          // Default to web
          return 'web';
        }

        function attemptAppRedirect() {
          const platform = detectPlatform();

          if (platform === 'ios' || platform === 'android') {
            // First, try to open the app with the deep link
            const deepLink = APP_SCHEME + currentPath.substring(1); // Remove leading slash

            // Create a hidden iframe to attempt app launch
            const iframe = document.createElement('iframe');
            iframe.style.display = 'none';
            iframe.src = deepLink;
            document.body.appendChild(iframe);

            // Set a timeout to redirect to store if app doesn't open
            setTimeout(function() {
              // If we're still on this page, the app didn't open
              if (document.hidden || document.webkitHidden) {
                // App opened successfully, do nothing
                return;
              }

              // Redirect to appropriate store
              if (platform === 'ios') {
                window.location.href = APP_STORE_URL;
              } else {
                window.location.href = PLAY_STORE_URL;
              }
            }, 2500);

            // Clean up iframe after attempt
            setTimeout(function() {
              if (iframe.parentNode) {
                iframe.parentNode.removeChild(iframe);
              }
            }, 3000);
          } else {
            // Desktop/web - redirect to website
            window.location.href = WEBSITE_URL;
          }
        }

        function redirect() {
          attemptAppRedirect();
        }

        // Set up manual redirect link
        document.getElementById('manual-redirect').addEventListener('click', function(e) {
          e.preventDefault();
          redirect();
        });

        // Auto-redirect after a short delay to show the loading state
        setTimeout(redirect, 1500);
      })();
    </script>
  </body>
</html>

Note what this code does: it checks the device type of the user and redirects them to the correct app store, or to a fallback website if that doesn't work. This approach isn't bulletproof, but is surprisingly effective given its simplicity.

Next, you need two configuration files, one for iOS and one for Android. Create a folder and call it .well-known/. Then create two files inside it:

public/ ├── index.html └── .well-known/ ├── apple-app-site-association └── assetlinks.json



In the apple-app-site-association file, paste the following code, replacing the placeholders with your own app details:
{
  "applinks": {
    "apps": [],
    "details": [
      {
        "appIDs": [
          "TEAM_ID.com.yourcompany.yourapp"
        ],
        "paths": [
          "*"
        ],
        "components": [
          {
            "/": "/*"
          }
        ]
      }
    ]
  },
  "webcredentials": {
    "apps": [
      "TEAM_ID.com.yourcompany.yourapp"
    ]
  }
}

Note that the TEAM_ID is your Apple Developer Team ID, which you can find in your Apple Developer account dashboard under "Membership", and com.yourcompany.yourapp is your app's bundle identifier, which you can find in FlutterFlow.

In the assetlinks.json file, paste the following code, replacing the bundle ID placeholder. For the SHA256 fingerprint, we'll return to this momentarily.

[{
  "relation": ["delegate_permission/common.handle_all_urls"],
  "target": {
    "namespace": "android_app",
    "package_name": "com.yourcompany.yourapp",
    "sha256_cert_fingerprints":
    ["<WE'LL FILL THIS IN LATER>"]
  }
}]

Now deploy with

> firebase deploy
and your webpages will be assigned a .web.app URL based on your project ID. When you visit, it'll redirect you, but you can also visit the configuration files directly to check they're working:

https://[your-project-id].web.app/.well-known/apple-app-site-association

What to do in FlutterFlow

FlutterFlow allows us to modify configuration files, and there are two we must edit to point to the webpages we just deployed.

The first is Runner.entitlements, which you can find in the Custom Code section in FlutterFlow. Add a new Entitlement and paste in the following, replacing the placeholder with your own project ID.

<key>com.apple.developer.associated-domains</key>
<array>
  <string>applinks:[your-project-id].web.app</string>
</array>


That takes care of iOS, and for Android, it's the AndroidManifest.xml file. Create an Activity Tag using the plus button at the top right, and paste in this code, again replacing [your-project-id].web.app with whatever your Firebase Hosted URL is (no need to add "https").

<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" />
    <data android:scheme="http" android:host="[your-project-id].web.app" />
    <data android:scheme="https" />
</intent-filter>

Getting the SHA256 Fingerprint

We're still missing the SHA256 fingerprint for the Android configuration file assetlinks.json. Actually we need two – one for local development and one for production.

The production key can be found in the Play Store, under

Test and release > App Integrity > App signing > SHA-256 certificate fingerprint

This assumes that your app has been uploaded to the Play Store, at least in draft status.

It's also a good idea to have a locally generated SHA-256 key so you can test the deep linking before deployment, and for this, we'll need the APK.

Running the app locally

I won't get into code downloading from FlutterFlow or local run instructions here, but you may use the FlutterFlow desktop app or vscode to get it going – there are plenty of guides for doing this online. In this case, we're looking for the SHA-256 key.

Once you have the code downloaded, open a terminal and navigate to the folder containing your code. Then run

> flutter build apk --release
to generate a release APK. This will take a few minutes.

Once the build is complete, drag the file into the terminal and run this command:

keytool -printcert -jarfile '/path/to/your/apk-file.apk'

If you don't have the keytool command installed, use the AI to help you install it on your machine.

This will output a bunch of information, but what you're looking for is:


Certificate fingerprints:
  SHA1: CD:D3:8A:6F:C4:D2:F3:1E:73:76:D6:03:72:3G:2C:C1:98:5F:59:37
  SHA256: 32:99:15:39:C6:84:23:AC:this-is-just-an-example:E4C3:CD:DB:18:33:EB:79:B7:A9:F1
        

Now copy the SHA256 fingerprint, along with the production one from the Play Store, into the assetlinks.json file, like so:

[{
  "relation": ["delegate_permission/common.handle_all_urls"],
  "target": {
    "namespace": "android_app",
    "package_name": "com.yourcompany.yourapp",
    "sha256_cert_fingerprints":
    ["32:99:15:39:C6:FC:62:ED:69:C3:CD:DB:18:33:EB:79:B7:A9:F1",   🢀 Local SHA-256
     "66:77:88:99:AA:BB:CC:D9AA:BB:CC:DD:EE:FF:00:11:22:33:44"]    🢀 Play store SHA-256
  }
}]

Final steps and testing

Now that the SHA-256 keys are in place, redeploy the webpages to Firebase Hosting with

> firebase deploy

You may now run your emulator or local device, and try visiting the .web.app link. Try first without the app installed, the link should navigate to the Play Store or App Store. Then install the app, and try again. The app should open.

In FlutterFlow, the names you've allocated to your screens are the ones you'll use as the route, and page parameters will be passed as query strings. For example, if you have a screen called "Shopping", with a parameter called "purchaseId", the deep link to open that screen with a purchaseId of 123 would be:

https://your-project-id.web.app/Shopping?purchaseId=123

Note that when passing the deep link, it's actually case sensitive, and uses the screen's name, not its route.

You can use a link shortener if you don't like how this is formatted, or you can set up a backend service that generates alias links for you.


And that's it. 💪

Remember, you'll need to test on a simulator or real device (test mode will not work), and the deep link must be tapped on – you cannot visit it in a browser. On an iOS device, the link will take you to the installed app or the App Store, on Android it'll take you to the app or the Play Store, and on web it'll redirect to whichever website you've set in index.html.

Need further guidance?

If you need help with any aspect of your mobile application development, maybe I can help. Simply follow the link to find out more.

  • Expert guidance
  • Learning materials
  • Consulting hours