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:
- A user is viewing a screen in your app (e.g.
/product/123). - The user taps a share button.
- Your app calls
createLink()with the current route and optional metadata. - The SDK creates the link on the OpenLynk backend and returns a shareable URL.
- 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.
Via metadata (recommended)
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},
);
Referral links
sdk.createLink(
destination: '/signup',
metadata: {'referrer_id': currentUserId, 'utm_source': 'referral'},
);
Campaign links
sdk.createLink(
destination: '/promo/summer',
metadata: {
'utm_source': 'email',
'utm_medium': 'newsletter',
'utm_campaign': 'summer_2025',
'promo_code': 'SAVE20',
},
);
Best Practices
- Generate on demand. Create links when users tap share, not pre-emptively. This avoids unused links cluttering your analytics.
- Include UTM parameters. Add
utm_source,utm_medium, andutm_campaignto metadata so you can track where links come from. - Cache generated links. If a user shares the same screen multiple times in a session, reuse the link instead of creating a new one.
- Handle errors gracefully. Show a user-friendly message if link creation fails. Never expose raw error details to end users.
- Show a loading state. Disable the share button and show a spinner while the API call is in progress to prevent duplicate taps.
- Use descriptive metadata. Include IDs, names, and context so the receiving app has everything it needs to render the destination screen.
Troubleshooting
| Symptom | Likely Cause | Fix |
|---|---|---|
createLink() throws "destination is required" | Missing destination parameter | Pass destination with the in-app route |
createLink() throws "destination must be a relative path" | Absolute URL passed instead of a route | Use paths like /product/123, not full URLs |
createLink() throws "Invalid API key" | Wrong or revoked API key | Generate a new key from the dashboard |
| Link opens web instead of the app | Universal Links or App Links not configured | See iOS Universal Links or Android App Links |
| Link opens app but navigates to wrong screen | Route parsing mismatch | Verify your deep link handler matches the destination format |
What's Next?
- Handle deep links to navigate users to the right screen when they open a shared link.
- Implement deferred deep linking to preserve link context across the app install flow.
- Learn more about how deep linking works under the hood.