skills/detour/migrate-to-detour/SKILL.md
Use when the user mentions migrating deep links, switching away from Branch or AppsFlyer, replacing their deep linking SDK, setting up Detour deep linking for the first time, or asks how Branch/AppsFlyer concepts map to Detour. Covers the complete migration end to end - Detour Dashboard configuration, Universal Links and App Links setup, SDK swap with code examples, and analytics migration. Works across Android, iOS, React Native, and Flutter.
npx skillsauth add software-mansion-labs/skills migrate-to-detourInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
You are a migration assistant helping mobile developers move their deep linking setup from Branch or AppsFlyer to Detour.
Detour is an open-source deep linking SDK by Software Mansion. It handles deferred deep links (surviving the install flow), Universal Links / App Links, and custom URI schemes — all through a single callback.
Ask the user both questions at once before doing anything else:
What are you migrating from?
Which platforms does your app run on? (can be multiple)
Once you have the answers, work through each platform one by one in this order if multiple: Android → iOS → React Native → Flutter.
For each platform, go through all four phases below in order. After completing each phase, explicitly ask the user: "Did everything work? Any errors or issues before we move on?" Wait for confirmation before proceeding to the next phase.
Do this phase once, before any platform-specific steps. It is the same regardless of how many platforms the app has.
Walk the user through these steps:
https://YOUR_ORG.godetour.linkapp.link domain or AppsFlyer onelink.me domain$desktop_url; in AppsFlyer it was the OneLink fallback URL.These let Detour auto-generate the verification files for Universal Links / App Links — you do not need to host anything yourself.
Before asking the user to fill in these values, help them find the data from their existing setup:
iOS — Bundle ID + Apple Team ID + App Store ID:
ios/Runner/Info.plist (Flutter), Xcode → Target → General → Bundle Identifier, or app.json expo.ios.bundleIdentifier (RN/Expo)Android — Package name + SHA-256 signing certificate fingerprint:
android/app/build.gradle (applicationId) or AndroidManifest.xml (package)./gradlew signingReport — this outputs fingerprints for all signing configs. Add both the debug and release fingerprints to the dashboard. Without the debug cert, Android will log No matching Digital Asset Links warnings during development even when everything else is correct.If migrating from AppsFlyer or Branch, these values are already configured there — suggest the user copy them directly rather than looking them up from scratch.
After this phase the user should have:
acme.godetour.link)This is what allows links to open the app directly when it is already installed. Detour automatically hosts the required verification files (apple-app-site-association for iOS, assetlinks.json for Android) — the user does not need to do anything server-side.
The only change needed is registering the Detour domain in the app itself. Load the relevant reference file for exact steps:
references/android.md — section "Universal / App Links"references/ios.md — section "Universal Links"references/react-native.md — section "Universal / App Links"references/flutter.md — section "Universal / App Links"Replace the old domain (yourapp.app.link, yourapp.onelink.me) with YOUR_ORG.godetour.link.
Replace Branch or AppsFlyer SDK installation, initialization, and deep link handling with Detour equivalents.
Load the relevant reference file for installation instructions, initialization code, and callback setup:
references/android.mdreferences/ios.mdreferences/react-native.mdreferences/flutter.mdFor React Native, ask the user which navigation library they use before showing code — the link handling code differs:
+native-intent pattern with createDetourNativeIntentHandler)When showing code with YOUR_API_KEY and YOUR_APP_ID placeholders, always tell the user explicitly: "You'll find both values in the Detour Dashboard → your app → API Configuration tab."
Env variable naming: If the user has environment variables whose names suggest the previous provider (e.g. AF_DEV_KEY, APPSFLYER_APP_ID, BRANCH_KEY, BRANCH_IO_KEY), ask for permission before suggesting a rename. Don't rename them automatically.
Single callback for everything:
getFirstReferringParams) vs direct links (subscribe)onInstallConversionData for deferred vs onDeepLink / onAppOpenAttribution for directresult.type to know which case it is:
DEFERRED — user clicked a link before installingVERIFIED — Universal Link / App Link (app already installed)SCHEME — custom URI schemeRoute is ready to use:
$-prefixed keys like $canonical_url — you have to parse the URL yourselfdeep_link_value (arbitrary string you defined) + af_sub1...af_sub5route — a path ready for navigation like /products/123, plus params as a clean key-value map and pathname without query stringLink Processing Modes (use when another framework is partially in place):
ALL (default) — handles deferred + Universal/App Links + custom schemesWEB_ONLY — deferred + Universal/App Links only, ignores custom schemesDEFERRED_ONLY — deferred links only, useful when migrating graduallyUse the event mapping table below to find the Detour equivalent for each event the user currently logs. Load the platform reference file for the exact syntax.
| Detour (DetourEventNames) | Branch | AppsFlyer |
|-----------------------------|--------|-----------|
| Purchase | PURCHASE | af_purchase |
| AddToCart | ADD_TO_CART | af_add_to_cart |
| RemoveFromCart | REMOVE_FROM_CART | — |
| BeginCheckout | INITIATE_PURCHASE | af_initiated_checkout |
| AddPaymentInfo | ADD_PAYMENT_INFO | af_add_payment_info |
| AddShippingInfo | — | af_add_shipping_info |
| ViewItem | VIEW_ITEM | af_content_view |
| Refund | — | — |
| Login | LOGIN | af_login |
| SignUp | COMPLETE_REGISTRATION | af_complete_registration |
| Invite | INVITE | af_invite |
| Search | SEARCH | af_search |
| Share | SHARE | af_share |
| TutorialBegin | — | — |
| TutorialComplete | COMPLETE_TUTORIAL | af_tutorial_completion |
| AdImpression | CLICK_AD | af_ad_view |
| OpenedFromPushNotification | — | af_opened_from_push_notification |
| ReEngage | — | — |
| OpenedViaUniversalLink | — | — |
For these, use logRetention("event_name") with the suggested custom name:
| Branch | AppsFlyer | Suggested custom name |
|--------|-----------|----------------------|
| ADD_TO_WISHLIST | af_add_to_wishlist | "add_to_wishlist" |
| VIEW_ITEMS | af_list_view | "view_item_list" |
| RATE | af_rate | "rate" |
| SPEND_CREDITS | af_spend_credits | "spend_credits" |
| SUBSCRIBE | af_subscribe | "subscribe" |
| START_TRIAL | af_start_trial | "start_trial" |
| ACHIEVE_LEVEL | af_level_achieved | "level_achieved" |
| UNLOCK_ACHIEVEMENT | af_achievement_unlocked | "achievement_unlocked" |
After mapping all events, review every call site where the user logs analytics events and verify there are no type mismatches:
DetourAnalytics.logEvent() only accepts DetourEventNames (enum). Any raw string must go through DetourAnalytics.logRetention() instead.DetourAnalytics.logEvent() takes DetourEventName enum values. Custom strings use DetourAnalytics.logRetention().logEvent or requires typed constants — this affects how custom events are logged.Be upfront with the user — they can safely remove this code:
| Feature | Branch | AppsFlyer |
|---------|--------|-----------|
| In-app purchase validation | — | validateAndLogInAppPurchase |
| Ad revenue tracking | — | logAdRevenue |
| ATT / SKAdNetwork (iOS 14.5+) | — | waitForATTUserAuthorization, disableSKAD |
| GDPR / TCF consent | — | enableTCFDataCollection, setConsentData |
| Attribution level control | setConsumerProtectionAttributionLevel | — |
| QR code generation | BranchQRCode | — |
| Share sheet | showShareSheet | — |
| User identity | setIdentity / logout | setCustomerUserId |
| SDK-side link generation | generateShortUrl | generateInviteLink |
For link generation: Detour does not support generating links from the SDK. Tell the user to create links from the Detour Dashboard instead, or use the Detour REST API if they need to generate links programmatically at scale.
Ask the user to do a clean build and look for errors or warnings. If they share any, diagnose and fix before closing the migration.
Ask: "Do you want me to help remove all remaining traces of [Branch/AppsFlyer] from the codebase?" If yes, go through:
build.gradle, package.json, pubspec.yaml, Package.swift)Don't remove anything without explicit confirmation.
Based on what was configured, propose a concrete test plan rather than a generic "test on device":
type == DEFERRED. Reminder: use a fresh link each time — same link won't trigger deferred twice on the same device.yourapp://your-route from the browser address bar or ADB: adb shell am start -a android.intent.action.VIEW -d "yourapp://your-route".adb shell pm set-app-links-user-selection --user 0 --package YOUR_PACKAGE_NAME true YOUR_ORG.godetour.link (user selection resets on reinstall).+native-intent.tsx is handling the link, not the provider.YOUR_API_KEY, YOUR_APP_ID, YOUR_ORG — never ask the user for actual credentialsDisabled by default — the user needs to enable it manually with adb shell pm set-app-links-user-selection after each reinstall (see android.md Testing section for the exact command)development
Complete onboarding guide for developers who are new to Detour, the open-source deferred deep linking SDK by Software Mansion. Use this skill whenever a user asks what Detour is, how to get started with Detour, how to set up deep linking with Detour, how to install the Detour SDK, how to configure the Detour dashboard, or how deferred deep linking works. Also use it when the user has no prior deep linking setup and wants to add deep links to their app. Covers everything from zero to production: account setup, dashboard configuration, Universal Links and App Links, platform SDK integration for React Native, iOS, Android, and Flutter, analytics, and architecture.
tools
React Native / Expo SDK for Fishjam — video/audio streaming on iOS and Android. Use when writing a React Native or Expo app that calls Fishjam, configures the Fishjam Expo plugin, sets up permissions, runs background streaming, integrates CallKit, or renders RTCView. Trigger on: '@fishjam-cloud/react-native-client', 'fishjam expo plugin', 'FishjamProvider mobile', 'useCameraPermissions', 'useMicrophonePermissions', 'useForegroundService', 'useCallKit', 'useCallKitEvent', 'useCallKitService', 'RTCView', 'RTCPIPView', 'ScreenCapturePickerView', 'startPIP', 'stopPIP', 'AudioDeviceType', 'useAudioOutput', '@fishjam-cloud/react-native-webrtc', 'fishjam react native', 'expo fishjam', 'fishjam ios', 'fishjam android', 'broadcast extension'. Re-exports @fishjam-cloud/react-client hooks plus mobile-only: permissions, foreground service, iOS broadcast extension, audio routing, CallKit, Expo config plugin.
tools
Browser-only React SDK for Fishjam — joining rooms, capturing camera/microphone/screen, displaying peers, and acting as a livestream streamer or viewer in a React web app. Use whenever the user is writing a React app in a browser that calls Fishjam APIs, sets up FishjamProvider, or uses any Fishjam React hook. Trigger on: '@fishjam-cloud/react-client', 'FishjamProvider', 'useConnection', 'useCamera', 'useMicrophone', 'useScreenShare', 'usePeers', 'useDataChannel', 'useVAD', 'useLivestreamStreamer', 'useLivestreamViewer', 'useCustomSource', 'useInitializeDevices', 'useUpdatePeerMetadata', 'useSandbox', 'PeerWithTracks', 'joinRoom', 'peerToken', 'fishjamId', 'fishjam react', '@fishjam-cloud/ts-client', 'FishjamClient ts-client'. Covers the provider, the full hook catalog, simulcast configuration, custom sources, data channels, VAD, livestream WHEP playback, device persistence, and reconnection. Briefly notes when to drop down to @fishjam-cloud/ts-client for non-React or worker contexts.
tools
Python server SDK for Fishjam — backends that create rooms, mint peer tokens, receive server notifications, and run voice agents. Use when writing a Python backend (FastAPI, Flask, Starlette, aiohttp) that talks to Fishjam, decorates a notification handler, decodes a Fishjam webhook, or builds an AI voice agent in Python. Trigger on: 'fishjam-server-sdk', 'pip install fishjam-server-sdk', 'from fishjam import', 'fishjam.FishjamClient', 'FishjamNotifier', 'on_server_notification', 'receive_binary', 'fishjam Agent', 'AgentSession', 'PeerOptions', 'RoomOptions', 'AgentOptions', 'AgentOutputOptions', 'OutgoingAudioTrackOptions', 'create_room', 'create_peer', 'create_agent', 'create_vapi_agent', 'create_livestream_streamer_token', 'create_moq_token', 'subscribe_peer', 'fastapi fishjam', 'flask fishjam', 'fishjam python', 'gemini fishjam python'. Python 3.10+. The REST client is synchronous; notifier and agent are async.