Handle Deep Links
This guide shows how to receive and process incoming deep links so your app navigates users to the correct screen when they open an OpenLynk link.
Goal
When a user taps an OpenLynk deep link, your app should:
- Intercept the incoming URL.
- Let the SDK resolve the link.
- Navigate to the destination screen with all attached metadata.
How the SDK Processes Incoming Links
The SDK handles the heavy lifting of link resolution:
- Intercepts the URL -- on Flutter this is automatic via the
app_linkspackage; on iOS and Android you pass the URL to the SDK manually. - Extracts the slug from the URL path (e.g.
1709876543-x7k9m2fromhttps://myapp.openlynk.to/1709876543-x7k9m2). - Fetches link details from the OpenLynk API using the slug.
- Calls your
onDeepLinkcallback with aParsedDeepLinkobject containing all the resolved data.
The ParsedDeepLink Object
When the SDK resolves a link, it delivers a ParsedDeepLink to your callback with the following fields:
| Field | Type | Description |
|---|---|---|
destinationPath | String | The in-app route (e.g. /product/123) |
parameters | Map | All metadata key-value pairs from link creation |
destination | String | Full destination including query parameters |
metadata | Map | Raw metadata from the link |
linkId | String | The OpenLynk link ID |
slug | String | The URL slug |
Platform-Specific Handling
Flutter
The Flutter SDK automatically listens for incoming Universal Links and App Links via the app_links package. You only need to provide the onDeepLink callback during initialization.
final sdk = OpenlynkSDK(
appId: 'YOUR_APP_ID',
apiKey: 'YOUR_API_KEY',
config: OpenlynkSDKConfig(
onDeepLink: (parsed) {
// Navigate to the destination
navigatorKey.currentState?.pushNamed(
parsed.destinationPath,
arguments: parsed.parameters,
);
},
),
);
await sdk.init();
init() also processes a cold-start link automatically. If the app was launched by tapping a deep link, onDeepLink fires during initialization.
Manual Parsing
If you receive a link URL from another source (e.g. clipboard or a custom channel), you can parse it manually:
final uri = Uri.parse('https://myapp.openlynk.to/1709876543-x7k9m2');
final parsed = await sdk.parseDeepLink(uri);
if (parsed != null) {
navigatorKey.currentState?.pushNamed(
parsed.destinationPath,
arguments: parsed.parameters,
);
}
iOS
The iOS SDK does not start an automatic link listener. You must call handleIncomingURL(_:) from your SceneDelegate or AppDelegate whenever a Universal Link arrives.
// SceneDelegate — cold start
func scene(
_ scene: UIScene,
willConnectTo session: UISceneSession,
options connectionOptions: UIScene.ConnectionOptions
) {
// ... SDK initialization with onDeepLink callback ...
if let userActivity = connectionOptions.userActivities.first,
userActivity.activityType == NSUserActivityTypeBrowsingWeb,
let url = userActivity.webpageURL {
openlynkSDK.handleIncomingURL(url)
}
}
// SceneDelegate — app already running
func scene(_ scene: UIScene, continue userActivity: NSUserActivity) {
guard userActivity.activityType == NSUserActivityTypeBrowsingWeb,
let url = userActivity.webpageURL else { return }
openlynkSDK.handleIncomingURL(url)
}
// SceneDelegate — URL contexts
func scene(_ scene: UIScene,
openURLContexts URLContexts: Set<UIOpenURLContext>) {
guard let url = URLContexts.first?.url else { return }
openlynkSDK.handleIncomingURL(url)
}
For apps without SceneDelegate:
// AppDelegate
func application(
_ application: UIApplication,
continue userActivity: NSUserActivity,
restorationHandler: @escaping ([UIUserActivityRestoring]?) -> Void
) -> Bool {
guard userActivity.activityType == NSUserActivityTypeBrowsingWeb,
let url = userActivity.webpageURL else { return false }
openlynkSDK.handleIncomingURL(url)
return true
}
If you forget to call handleIncomingURL, the SDK will never know about incoming links and onDeepLink will never fire on iOS.
Android
The Android SDK does not start an automatic link listener. You must call handleIncomingUri from your Activity's onCreate and onNewIntent.
Kotlin
class MainActivity : AppCompatActivity() {
private val sdk: OpenlynkSDK
get() = (application as MyApplication).openlynkSDK
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
intent?.data?.let { uri ->
sdk.handleIncomingUri(uri)
}
}
override fun onNewIntent(intent: Intent) {
super.onNewIntent(intent)
intent.data?.let { uri ->
sdk.handleIncomingUri(uri)
}
}
}
Java
public class MainActivity extends AppCompatActivity {
private OpenlynkSDK sdk;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
sdk = ((MyApplication) getApplication()).getOpenlynkSDK();
Uri uri = getIntent().getData();
if (uri != null) {
sdk.handleIncomingUri(uri, null);
}
}
@Override
protected void onNewIntent(Intent intent) {
super.onNewIntent(intent);
if (intent.getData() != null) {
sdk.handleIncomingUri(intent.getData(), null);
}
}
}
You must handle the URI in both onCreate (cold start) and onNewIntent (app already in memory). Missing either one causes deep links to silently fail in that scenario.
Building a Router
Once the SDK calls onDeepLink, you need to route the user to the correct screen. Here is a simple path-based routing pattern for each platform.
Flutter
void navigateTo(String path, Map<String, dynamic> params) {
final navigator = navigatorKey.currentState;
if (navigator == null) return;
if (path.startsWith('/product/')) {
final productId = path.split('/').last;
navigator.push(MaterialPageRoute(
builder: (_) => ProductPage(productId: productId),
));
} else if (path.startsWith('/profile/')) {
final userId = path.split('/').last;
navigator.push(MaterialPageRoute(
builder: (_) => ProfilePage(userId: userId),
));
} else {
navigator.pushNamed(path, arguments: params);
}
}
iOS (Swift)
func navigateTo(path: String, params: [String: Any]) {
guard let nav = window?.rootViewController
as? UINavigationController else { return }
if path.hasPrefix("/product/") {
let productId = String(path.split(separator: "/").last ?? "")
let vc = ProductViewController(productId: productId)
nav.pushViewController(vc, animated: true)
} else if path.hasPrefix("/profile/") {
let userId = String(path.split(separator: "/").last ?? "")
let vc = ProfileViewController(userId: userId)
nav.pushViewController(vc, animated: true)
}
}
Android (Kotlin)
object DeepLinkRouter {
fun navigate(context: Context, path: String, params: Map<String, Any>) {
val intent = when {
path.startsWith("/product/") -> {
val productId = path.split("/").last()
Intent(context, ProductActivity::class.java).apply {
putExtra("product_id", productId)
}
}
path.startsWith("/profile/") -> {
val userId = path.split("/").last()
Intent(context, ProfileActivity::class.java).apply {
putExtra("user_id", userId)
}
}
else -> Intent(context, MainActivity::class.java)
}
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
context.startActivity(intent)
}
}
Push Notification Deep Links
On Flutter, push notifications that contain an OpenLynk destination can be processed using handlePushPayload(). This creates the same ParsedDeepLink object and delivers it through the onDeepLink callback, so your navigation code stays unified.
FirebaseMessaging.onMessageOpenedApp.listen((message) {
if (message.data.containsKey('destinationPath')) {
sdk.handlePushPayload(message.data);
// This triggers onDeepLink with a ParsedDeepLink
}
});
See Set Up Push Notifications for the full push integration guide.
Testing Deep Links
Flutter
Run the app in debug mode and use adb (Android) or the Notes app (iOS) to open a link. Check your console logs for onDeepLink output.
iOS
- Open the Notes app on a physical device.
- Type or paste a link URL and long-press it.
- Tap "Open in [Your App]".
- Verify
onDeepLinkfires with the correctdestinationPathandparameters.
Universal Links do not work in the iOS Simulator. Always test on a physical device.
Android
Use ADB to simulate an App Link:
adb shell am start -a android.intent.action.VIEW \
-d "https://YOUR_APP_SLUG.openlynk.to/test-link" \
com.example.myapp
Or send yourself a link via email or messaging and tap it on your device.
What's Next?
- Implement link sharing to let users create shareable deep links.
- Implement deferred deep linking to preserve link context across the app install flow.
- Learn more about how deep linking works under the hood.