What does Flutter's deep-linking flag do, and when do you set flutter_deeplinking_enabled or FlutterDeepLinkingEnabled to false?
answer
- engine forwards links to the framework
- on by default since 3.27
- meta-data on the Android activity
- key in the iOS Info.plist
- turn off when a plugin owns links
basics
~20 sThe flag makes the Flutter engine forward incoming links to the framework as route information and defaults to true since Flutter 3.27; set it to false only when a plugin such as app_links handles links, since Flutter's handler breaks such plugins.
solid answer
~40 sFlutter's embedding can deliver an incoming link to Dart itself: on Android the intent's data URI becomes the initial route or a `pushRouteInformation` message, and on iOS the URL is sent to the framework from the app or scene delegate. The **deep-linking flag** switches that behaviour. It is read from `<meta-data android:name="flutter_deeplinking_enabled" .../>` on the Android activity and from the `FlutterDeepLinkingEnabled` key in `Info.plist`, and since Flutter 3.27 it defaults to **true** when absent; earlier versions required opting in. I set it to `false` only when a plugin such as `app_links` owns link handling, because the Flutter docs warn that the default handler breaks such plugins; with the flag off, the router no longer receives links, so the plugin must route them.
code
xml · 8 lines<!-- AndroidManifest.xml: only when a plugin such as app_links owns links -->
<activity android:name=".MainActivity">
<meta-data android:name="flutter_deeplinking_enabled" android:value="false" />
</activity>
<!-- ios/Runner/Info.plist: the iOS counterpart -->
<key>FlutterDeepLinkingEnabled</key>
<false/>go deeper
Recall that the flag lets the Flutter engine forward links to the framework, defaults to true since 3.27, and is set to false when a link plugin takes over.
Explain where each platform reads the flag, how Android turns the intent into an initial route or pushRouteInformation, and what iOS does with an unhandled https link.
Show you check the flag first when a Flutter upgrade breaks plugin-based links, and keep one owner for link handling rather than two.
Decide between Flutter's built-in handling and a link plugin, weighing campaign attribution needs against a simpler router-only path.
## What the flag controls A deep link arrives in native code first: an Android `Intent` with a data URI, or an iOS `openURL` or user-activity callback. Something must turn it into a location the Dart app understands. The Flutter engine can do that by itself, and the **deep-linking flag** decides whether it does: - **Android**: when enabled, `FlutterActivity` reads the intent's data URI and uses it as the **initial route** on a cold start, and on `onNewIntent` sends it to the framework as a `pushRouteInformation` message. - **iOS**: when enabled, the Flutter app or scene delegate sends the URL to the framework. For https (universal) links, if the framework reports the link unhandled, the engine reopens it with the system, which shows it in Safari. - **Dart side**: `WidgetsBinding` hands the route information to its observers; a `Router`'s route information provider, such as go_router's, accepts it and navigates. ## How a link travels, step by step 1. The user taps `https://tickets.example.com/events/42`; the platform decides, from verification, that the ticketing app handles it. 2. **Android**: the system starts or resumes `MainActivity` with an intent whose data is the URI. **iOS**: the system calls the app or scene delegate with the URL or a web-browsing user activity. 3. The embedding checks the deep-linking flag. If it is disabled, the link stops here for Flutter; only a plugin can pick it up. 4. If enabled, a cold Android start uses the URI as the **initial route**; otherwise the engine sends a `pushRouteInformation` message on the navigation channel. 5. In Dart, `WidgetsBinding` offers the resulting `RouteInformation` to its observers until one accepts it. 6. The `Router`'s route information provider accepts it and the parser, go_router's for example, turns the path into pages. 7. On iOS, if no observer accepted an https link, the engine reopens it with the system. The flag, in other words, controls step 3 only; everything before it is the platform, and everything after it is the framework and the router. ## Where it lives | Platform | Setting | Default when absent | |---|---|---| | Android | `<meta-data android:name="flutter_deeplinking_enabled" android:value="false" />` inside the `<activity>` | `true` | | iOS | `FlutterDeepLinkingEnabled` key in `ios/Runner/Info.plist` | `YES` | The engine source is explicit: Android's `deepLinkEnabled` returns `true` if the meta-data key is not found, and iOS's `isFlutterDeepLinkingEnabled` returns `YES` if the key is not set. ## The 3.27 change Before Flutter 3.27 the default was **false**, and every deep-linking tutorial told you to add the meta-data or plist key with `true`. The change, which landed in 3.25.0-0.1.pre and reached stable in 3.27, flipped the default to **true**. For an app using Flutter's own handling, the old opt-in entries are now redundant but harmless. For an app using a third-party plugin, it is a **breaking change**: the Flutter handler now competes with the plugin. ## When to set it to false The case the Flutter docs name is an app that uses a plugin which owns incoming links, such as `app_links`, `uni_links` or `flutter_branch_sdk`: Flutter's migration guide for the change lists these and says to set the flag to `false`, because the default handler breaks them. With the flag off, nothing reaches the router automatically. The plugin's stream or callback must hand each location to the router, typically by calling `router.go` with the link's path and query. ## Things the flag does not do - It does **not** verify anything. App Links and Universal Links still need the `autoVerify` intent filter with `assetlinks.json`, or the `applinks:` entitlement with `apple-app-site-association`. - It does **not** register a URL scheme or host; that is the manifest's intent filter or the `Info.plist` URL types. - It does **not** map URLs to screens; the router does. ## A practical check When links stopped working after a Flutter upgrade in an app that uses a link plugin, look for the flag first: an app that never set `false` got Flutter's handler switched on under it by 3.27.
- With Flutter's deep-linking flag enabled on Android, how does a link reach Dart on a cold start versus a warm one?On a cold start `FlutterActivity` uses the intent's data URI as the initial route. When the app is already running, `onNewIntent` sends it as a `pushRouteInformation` message, which `WidgetsBinding` hands to observers such as the router's route information provider.
- After upgrading to Flutter 3.27, why might an app using app_links start handling links twice or incorrectly?3.27 changed the flag's default from false to true, so Flutter's own handler now forwards links to the router while the plugin also handles them. Setting `flutter_deeplinking_enabled` and `FlutterDeepLinkingEnabled` to false restores the plugin as the only handler.
saying these in an interview costs you the question
- Believes the flag verifies App Links and Universal Links.
- Still adds the flag with true as a required step in Flutter 3.47.
- Leaves the flag at its default while an app_links plugin handles the same links.
- Thinks turning the flag off still delivers links to go_router.
- Expects the flag to register the https host or custom scheme.