Skip to main content

Implement Link Sharing

This guide shows how to let users share deep links to any screen in your app. When someone taps a shared link, they land directly on the right content -- even if they need to install the app first.

How It Works

The sharing flow has five steps:

  1. A user is viewing a screen in your app (e.g. /product/123).
  2. The user taps a share button.
  3. Your app calls createLink() with the current route and optional metadata.
  4. The SDK creates the link on the OpenLynk backend and returns a shareable URL.
  5. The user shares the URL via any channel (messages, social media, email, etc.).

When someone clicks the shared link:

  • App installed -- the app opens and navigates to the destination screen with all metadata.
  • App not installed -- the user is sent to the app store. After install, deferred deep linking restores the original destination.

Passing Parameters

You can pass data to the link recipient in two ways.

await sdk.createLink(
destination: '/product/123',
metadata: {
'product_id': '123',
'color': 'blue',
'size': 'large',
'utm_source': 'share',
},
);

Via destination query string

await sdk.createLink(
destination: '/product/123?color=blue&size=large',
);

Both approaches work. When there is a conflict, metadata keys take precedence over query string parameters.

Platform Examples

Each example shows a complete share button implementation that generates a link on tap and opens the platform share sheet.

Flutter

Uses the share_plus package for the native share sheet.

import 'package:openlynk_sdk/openlynk_sdk.dart';
import 'package:share_plus/share_plus.dart';

class ProductPage extends StatefulWidget {
final OpenlynkSDK sdk;
final String productId;

const ProductPage({required this.sdk, required this.productId, super.key});


State<ProductPage> createState() => _ProductPageState();
}

class _ProductPageState extends State<ProductPage> {
bool _isSharing = false;

Future<void> _share() async {
setState(() => _isSharing = true);
try {
final link = await widget.sdk.createLink(
destination: '/product/${widget.productId}',
metadata: {
'product_id': widget.productId,
'utm_source': 'share',
'utm_medium': 'app',
},
);
await Share.share('Check this out: ${link.url}');
} catch (e) {
if (mounted) {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(content: Text('Could not create link')),
);
}
} finally {
if (mounted) setState(() => _isSharing = false);
}
}


Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Product ${widget.productId}'),
actions: [
IconButton(
icon: _isSharing
? const SizedBox(
width: 20, height: 20,
child: CircularProgressIndicator(strokeWidth: 2),
)
: const Icon(Icons.share),
onPressed: _isSharing ? null : _share,
),
],
),
body: Center(child: Text('Product ${widget.productId}')),
);
}
}

iOS (Swift)

Uses UIActivityViewController for the native share sheet.

func shareProduct(productId: String) {
sdk.createLink(
destination: "/product/\(productId)",
metadata: [
"product_id": productId,
"utm_source": "share",
"utm_medium": "ios_app"
]
) { [weak self] result in
DispatchQueue.main.async {
switch result {
case .success(let link):
let activityVC = UIActivityViewController(
activityItems: ["Check this out!", link.url],
applicationActivities: nil
)
self?.present(activityVC, animated: true)
case .failure(let error):
print("Error: \(error)")
}
}
}
}

Android (Kotlin)

Uses Intent.ACTION_SEND for the system share sheet.

fun shareProduct(productId: String) {
sdk.createLink(
destination = "/product/$productId",
metadata = mapOf(
"product_id" to productId,
"utm_source" to "share",
"utm_medium" to "android_app"
)
) { result ->
runOnUiThread {
result.onSuccess { link ->
val intent = Intent(Intent.ACTION_SEND).apply {
type = "text/plain"
putExtra(Intent.EXTRA_TEXT, "Check this out: ${link.url}")
}
startActivity(Intent.createChooser(intent, "Share via"))
}
result.onFailure { error ->
Toast.makeText(this, "Error: ${error.message}",
Toast.LENGTH_SHORT).show()
}
}
}
}

Android (Java)

Uses Intent.ACTION_SEND with JSONObject for metadata.

void shareProduct(String productId) {
JSONObject metadata = new JSONObject();
try {
metadata.put("product_id", productId);
metadata.put("utm_source", "share");
metadata.put("utm_medium", "android_app");
} catch (JSONException e) { }

sdk.createLink("/product/" + productId, metadata,
new OpenlynkSDK.CreateLinkCallback() {
@Override
public void onSuccess(OpenlynkSDK.CreatedLink link) {
runOnUiThread(() -> {
Intent intent = new Intent(Intent.ACTION_SEND);
intent.setType("text/plain");
intent.putExtra(Intent.EXTRA_TEXT,
"Check this out: " + link.url);
startActivity(Intent.createChooser(intent, "Share via"));
});
}

@Override
public void onError(Exception error) {
runOnUiThread(() ->
Toast.makeText(ProductActivity.this,
"Error: " + error.getMessage(),
Toast.LENGTH_SHORT).show());
}
});
}

Common Use Cases

Product sharing

sdk.createLink(
destination: '/product/$productId',
metadata: {'product_id': productId},
);

Article sharing

sdk.createLink(
destination: '/article/$articleId',
metadata: {'title': articleTitle},
);
sdk.createLink(
destination: '/signup',
metadata: {'referrer_id': currentUserId, 'utm_source': 'referral'},
);
sdk.createLink(
destination: '/promo/summer',
metadata: {
'utm_source': 'email',
'utm_medium': 'newsletter',
'utm_campaign': 'summer_2025',
'promo_code': 'SAVE20',
},
);

Best Practices

  1. Generate on demand. Create links when users tap share, not pre-emptively. This avoids unused links cluttering your analytics.
  2. Include UTM parameters. Add utm_source, utm_medium, and utm_campaign to metadata so you can track where links come from.
  3. Cache generated links. If a user shares the same screen multiple times in a session, reuse the link instead of creating a new one.
  4. Handle errors gracefully. Show a user-friendly message if link creation fails. Never expose raw error details to end users.
  5. Show a loading state. Disable the share button and show a spinner while the API call is in progress to prevent duplicate taps.
  6. Use descriptive metadata. Include IDs, names, and context so the receiving app has everything it needs to render the destination screen.

Troubleshooting

SymptomLikely CauseFix
createLink() throws "destination is required"Missing destination parameterPass destination with the in-app route
createLink() throws "destination must be a relative path"Absolute URL passed instead of a routeUse paths like /product/123, not full URLs
createLink() throws "Invalid API key"Wrong or revoked API keyGenerate a new key from the dashboard
Link opens web instead of the appUniversal Links or App Links not configuredSee iOS Universal Links or Android App Links
Link opens app but navigates to wrong screenRoute parsing mismatchVerify your deep link handler matches the destination format

What's Next?