When a user taps an action in Storyteller that should route inside your app, subscribe to Storyteller.userNavigatedToApp.
import'dart:async';import'package:storyteller_sdk/storyteller_sdk.dart';latefinalStreamSubscription<String>_subscription;voidstartListeningToStorytellerLinks(){_subscription=Storyteller.userNavigatedToApp.listen((url){// Parse and route using your app's navigation system.// Example: myapp://product/123});}Future<void>stopListeningToStorytellerLinks()async{await_subscription.cancel();}
Queue incoming URLs until Storyteller.initialize(...) returns success: true; then process each queued URL once. The SDK does not install your Android intent filters or iOS associated domains.
If your app receives a URL (from your existing deep-link handling setup), you can forward Storyteller links to the SDK:
Check the URL with Storyteller.isStorytellerDeepLink(url)
If true, call Storyteller.openDeepLink(url) and handle errors. Detection recognizes Storyteller URLs; it does not confirm that the content exists or can be opened.
import'package:storyteller_sdk/storyteller_sdk.dart';Future<void>handleIncomingUrl(Stringurl)async{if(awaitStoryteller.isStorytellerDeepLink(url)){awaitStoryteller.openDeepLink(url);return;}// Otherwise, handle it as your app's own deep link.}
The earlier isStorytellerDeeplink and openDeeplink spellings remain
source-compatible aliases; both pairs use the same native channel calls.
Storyteller.openClipByExternalId(...) and its
openCollectionByExternalId(...) alias
An explicit StorytellerOpenReason keeps analytics attribution consistent
when your app has already classified the navigation source. If you omit it,
the native entry point keeps its own default.
Android's external-ID Clips entry point always owns deep-link attribution. On
Android, omit openReason or pass StorytellerOpenReason.deeplink; passing
StorytellerOpenReason.instanceMethod throws an UNSUPPORTED_ARGUMENTPlatformException. iOS supports both reasons for the same Flutter API.
In the Showcase app, DeepLinkService._handleIncomingLink validates App Links, queues them until the SDK is ready, and finally calls Storyteller.openDeeplink—use it as a working template for production apps.
If a recognized URL opens no content, verify initialization, the URL's content identifiers and publication state, and the platform link configuration. See Troubleshooting.
{"slug": "deeplinking", "page_title": "Deep Linking", "page_url": "Deeplinking/", "canonical_url": "/flutter/Deeplinking/", "markdown": "# Deep Linking\n\nDeep linking has two sides:\n\n1. **Opening Storyteller content from an incoming URL** (e.g. a user taps a Storyteller link and your app launches)\n2. **Navigating back to your app from Storyteller UI** (e.g. a Story page action button uses your app's URL scheme)\n\nThis page focuses on the Flutter SDK API surface. You still need the usual platform setup for app links/universal links on Android and iOS.\n\n## Navigate back to your app (`userNavigatedToApp`)\n\nWhen a user taps an action in Storyteller that should route inside your app, subscribe to `Storyteller.userNavigatedToApp`.\n\n```dart\nimport 'dart:async';\n\nimport 'package:storyteller_sdk/storyteller_sdk.dart';\n\nlate final StreamSubscription<String> _subscription;\n\nvoid startListeningToStorytellerLinks() {\n _subscription = Storyteller.userNavigatedToApp.listen((url) {\n // Parse and route using your app's navigation system.\n // Example: myapp://product/123\n });\n}\n\nFuture<void> stopListeningToStorytellerLinks() async {\n await _subscription.cancel();\n}\n```\n\n## Handle incoming Storyteller deep links\n\nQueue incoming URLs until `Storyteller.initialize(...)` returns `success: true`; then process each queued URL once. The SDK does not install your Android intent filters or iOS associated domains.\n\nIf your app receives a URL (from your existing deep-link handling setup), you can forward Storyteller links to the SDK:\n\n1. Check the URL with `Storyteller.isStorytellerDeepLink(url)`\n2. If `true`, call `Storyteller.openDeepLink(url)` and handle errors. Detection recognizes Storyteller URLs; it does not confirm that the content exists or can be opened.\n\n```dart\nimport 'package:storyteller_sdk/storyteller_sdk.dart';\n\nFuture<void> handleIncomingUrl(String url) async {\n if (await Storyteller.isStorytellerDeepLink(url)) {\n await Storyteller.openDeepLink(url);\n return;\n }\n\n // Otherwise, handle it as your app's own deep link.\n}\n```\n\nThe earlier `isStorytellerDeeplink` and `openDeeplink` spellings remain\nsource-compatible aliases; both pairs use the same native channel calls.\n\n## Related APIs\n\nIf you already know what you want to open, you can also use direct navigation helpers:\n\n- `Storyteller.openStory(..., openReason: StorytellerOpenReason.deeplink)`\n- `Storyteller.openCollection(..., openReason: StorytellerOpenReason.deeplink)`\n- `Storyteller.openCategory(..., openReason: StorytellerOpenReason.deeplink)`\n- `Storyteller.openClipByExternalId(...)` and its\n `openCollectionByExternalId(...)` alias\n\nAn explicit `StorytellerOpenReason` keeps analytics attribution consistent\nwhen your app has already classified the navigation source. If you omit it,\nthe native entry point keeps its own default.\n\nAndroid's external-ID Clips entry point always owns deep-link attribution. On\nAndroid, omit `openReason` or pass `StorytellerOpenReason.deeplink`; passing\n`StorytellerOpenReason.instanceMethod` throws an `UNSUPPORTED_ARGUMENT`\n`PlatformException`. iOS supports both reasons for the same Flutter API.\n\nSee [Storyteller API](AdditionalMethods.md) for the full list of helpers.\n\nIn the Showcase app, [`DeepLinkService._handleIncomingLink`](https://github.com/getstoryteller/storyteller-showcase-flutter/blob/main/lib/services/deep_link_service.dart#L50) validates App Links, queues them until the SDK is ready, and finally calls `Storyteller.openDeeplink`\u2014use it as a working template for production apps.\n\nIf a recognized URL opens no content, verify initialization, the URL's content identifiers and publication state, and the platform link configuration. See [Troubleshooting](Troubleshooting.md).\n", "copy_markdown_include_header": false, "base_path": "", "ai_dir": "ai", "missing_payload_behavior": "empty"}