Deep Links
Configure custom schemes, Universal Links, App Links, and routable destinations.
Deep links use two entry channels: the variant-specific custom URL scheme from
app.config.ts, and optional HTTPS Universal Links / Android App Links when
EXPO_PUBLIC_LINKING_DOMAIN is set.
Entry channels
| Channel | Status | Where it is configured |
|---|---|---|
| Custom scheme | Integrated | app.config.ts derives the scheme from APP_VARIANT |
| Universal Links | Integrated | ios.associatedDomains is generated when EXPO_PUBLIC_LINKING_DOMAIN exists |
| Android App Links | Integrated | android.intentFilters with autoVerify: true is generated for the same domain |
| Web links | Integrated | createAppLink() returns HTTPS URLs when the domain exists, otherwise an Expo-created URL |
Default variant values:
development -> soar-starter-expo-dev://
preview -> soar-starter-expo-preview://
production -> soar-starter-expo://Use the scheme that matches the build installed on the device.
App link domain
Set a public domain in .env.local or your EAS environment:
EXPO_PUBLIC_LINKING_DOMAIN=app.example.comapp.config.ts turns that into:
iOS: applinks:app.example.com
Android: https://app.example.com/* with autoVerifyYou still need to host the platform association files:
| Platform | File |
|---|---|
| iOS | https://app.example.com/.well-known/apple-app-site-association |
| Android | https://app.example.com/.well-known/assetlinks.json |
If the domain is absent, createAppLink() falls back to Expo Linking's custom
scheme URL.
Route resolver
lib/deep-links.ts keeps the allowed app destinations explicit:
/
/component
/showcase
/todos
/me
/profile
/notifications
/refer
/paywall
/redeem-code
/permissionsresolveDeepLinkHref(url) parses the URL, normalizes the path, preserves string
query params, and returns null for anything outside that allow-list.
openDeepLink(url, fallback) pushes the resolved route or the fallback.
The notification provider uses the same function for notification taps:
openDeepLink(url, "/notifications");In-app demos
app/showcase/deep-links.tsx demonstrates generated links for Home, Todos,
Notifications, Permissions, and Refer. It calls:
const url = createAppLink(path);
const href = resolveDeepLinkHref(url);
openDeepLink(url);The referral flow uses the same helper. lib/referrals.ts builds an invite link
with createAppLink("/"), and app/refer.tsx shares it through the native
share sheet.
Test links
Use Expo's URI helper for custom schemes:
npx uri-scheme open "soar-starter-expo-dev://todos" --ios
npx uri-scheme open "soar-starter-expo-dev://notifications" --androidPlatform-native alternatives are useful when debugging a simulator or emulator:
xcrun simctl openurl booted "soar-starter-expo-dev://refer"
adb shell am start -a android.intent.action.VIEW -d "soar-starter-expo-dev://refer"For HTTPS links, first host the association files, build with
EXPO_PUBLIC_LINKING_DOMAIN, install that build, then test:
npx uri-scheme open "https://app.example.com/refer" --ios
npx uri-scheme open "https://app.example.com/refer" --androidCustomize
- Add a routable destination to
ALLOWED_STATIC_PATHSbefore accepting inbound links to it. - Keep routes deterministic. Avoid passing secrets through query params because URLs can be logged by the OS, analytics, or crash tooling.
- Update
app.config.tsif you rename the bundle ID, package name, scheme, or linking domain. - Rebuild native apps after changing schemes, associated domains, or Android intent filters.
