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

ChannelStatusWhere it is configured
Custom schemeIntegratedapp.config.ts derives the scheme from APP_VARIANT
Universal LinksIntegratedios.associatedDomains is generated when EXPO_PUBLIC_LINKING_DOMAIN exists
Android App LinksIntegratedandroid.intentFilters with autoVerify: true is generated for the same domain
Web linksIntegratedcreateAppLink() 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.

Set a public domain in .env.local or your EAS environment:

EXPO_PUBLIC_LINKING_DOMAIN=app.example.com

app.config.ts turns that into:

iOS:     applinks:app.example.com
Android: https://app.example.com/* with autoVerify

You still need to host the platform association files:

PlatformFile
iOShttps://app.example.com/.well-known/apple-app-site-association
Androidhttps://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
/permissions

resolveDeepLinkHref(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.

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" --android

Platform-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" --android

Customize

  • Add a routable destination to ALLOWED_STATIC_PATHS before 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.ts if you rename the bundle ID, package name, scheme, or linking domain.
  • Rebuild native apps after changing schemes, associated domains, or Android intent filters.

On this page