Skip to content
All posts

5 min read

Deferred deep links in Flutter with the deeplinknow plugin

The deeplinknow package is on pub.dev at 1.3.0. How to set it up, and what it does about the things that keep breaking deferred links in Flutter apps: UIScene, association files, signing keys, the paste prompt and fingerprint matching.

deeplinknow 1.3.0 is on pub.dev. It's a federated plugin: the Android side is in the main package and the iOS side is deeplinknow_ios, which pub installs for you. Both are thin layers over the native DeepLinkNow iOS and Android SDKs. The clipboard read, the Play Install Referrer lookup, the fingerprint match and the once-per-install bookkeeping all run in Swift and Kotlin, so a Flutter app gets the same answer a native app would.

Setup

flutter pub add deeplinknow

Stop the app and rebuild after adding it, because hot restart doesn't load native code. It needs Flutter 3.24 or later, iOS 13 and Android minSdk 24.

import 'package:deeplinknow/deeplinknow.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';

Future<void> main() async {
  WidgetsFlutterBinding.ensureInitialized();

  // Never throws: null means it failed (see DeepLinkNow.lastError).
  await DeepLinkNow.initialize(
    'dln_pub_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx',
    config: const DLNConfig(enableLogs: kDebugMode),
  );

  // Delivered once per install; null on later launches.
  final link = await DeepLinkNow.checkDeferredDeepLink();

  runApp(MyApp(initialRoute: link?.path ?? '/'));
}

checkDeferredDeepLink() resolves to the path and query the user tapped before installing, or null, and doesn't throw. When it failed rather than found nothing, DeepLinkNow.lastError has the reason (network, invalidApiKey and so on). Use the public key (dln_pub_…) from the dashboard.

FlutterFlow works through a custom action: add deeplinknow: ^1.3.0 as a dependency, wrap the two calls in an action that returns link?.fullPath, and run it on page load of the first page. FlutterFlow's browser-based test mode always gets null because the plugin has no web implementation, so test on a device or emulator build. The Flutter docs have the full action.

The problems from r/FlutterDev

Two threads from August 2026 cover most of what goes wrong: one about iOS links breaking after the UIScene change, and a list of six things that silently break deferred deep linking. The plugin handles some of these. The rest are setup in your own project.

Flutter 3.38 added UIScene support, 3.41 made it the default and migrates an unmodified app delegate automatically, and Xcode 27 requires it (Flutter's breaking-change page, checked 21 October 2026). Under UIScene, iOS hands Universal Links to the scene delegate, so a plugin that only listens on the app delegate never sees them, and nothing logs an error. app_links 7.0.0 added scene delegate support.

The deeplinknow plugin covers the deferred case, the first launch after an install. Taps that open an app that's already installed still arrive through app_links, and those are the ones that broke. What the docs ask for: app_links 7 or later; FlutterDeepLinkingEnabled set to false in Info.plist, so the engine doesn't hand links it doesn't claim back to Safari; and a subscription to uriLinkStream at the top of initState, before any await, because the stream doesn't replay a link that arrived before you listened. DeepLinkNow.parseDeepLink turns that URI into the same shape as the deferred result.

Association files served wrong

The usual faults are a .json extension on apple-app-site-association, the wrong content type, a redirect, or a bot challenge page in front of it. Devices install Apple's CDN copy, which Apple fetches from its own servers, so a rule that blocks unknown clients breaks it while it still opens fine in your browser.

DLN generates apple-app-site-association and assetlinks.json for your link domains from the dashboard settings. They're served as JSON at the .well-known path with no redirect, and no firewall rule puts a challenge in front of them. The free AASA validator checks both your server's copy and Apple's CDN copy, which is the one iOS actually uses.

With Play App Signing, Google re-signs the app, so assetlinks.json needs the SHA-256 of the app signing key from Play Console. With only the upload key there, Play builds fail verification and links open in the browser. The dashboard asks for the Play App Signing certificate first and accepts more than one fingerprint, so the upload and debug keys can go in as well. The assetlinks checker points this out when it reads your file.

The paste prompt on iOS 16+

Reading the pasteboard on iOS 16 and later shows the "Allow Paste" prompt. The link page copies the link before sending the user to the App Store, and on first launch the SDK starts by asking iOS whether the pasteboard probably holds a web URL. That question doesn't prompt. The SDK reads the pasteboard only when the answer is yes, and at most once per install. To never show the prompt, pass useClipboard: false and the plugin goes straight to fingerprint matching.

Fingerprints are not exact

Without a clipboard link or a referrer, the server compares the device with recent clicks on your links: IP, device class, OS version, screen size, language, time zone and time since the click. iCloud Private Relay, carrier NAT and a phone moving from Wi-Fi to cellular between the tap and the first launch all change the IP or share it between users. An IP match isn't required, missing values never count as a match, and each result has a confidenceScore from 0 to 100 and a matchQuality of HIGH, MEDIUM or LOW. minConfidence is off by default: set one, or route HIGH matches directly and ask the user to confirm the rest.

Install versus reopen

The deferred link is delivered once per install. After the first definitive answer, later launches resolve null without touching the network, the clipboard or the referrer. A failed check doesn't count, so an offline first launch tries again next time. For testing, resetDeferredDeepLink() or force: true clears the state.

No install referrer

On Android the Play Install Referrer is the exact signal, but it only exists for installs from Google Play, and people report it arriving empty on managed work-profile accounts. When it's missing, the plugin falls back to the scored fingerprint match, as it does for a sideloaded build.

The Developer plan is free up to 25,000 monthly active users, with no card. The other plans are on the pricing page.

Try DeepLinkNow on your app.

Deep links and deferred deep links for iOS, Android, React Native and Flutter. Free to start.