Deep links turn your app into part of the web. A shared product link opens the product screen. A password reset email opens the reset form. A push notification lands on the exact chat. Expo Router makes this almost automatic, because every file is already a URL. Here's how to set up all three kinds of links and test them.
Every route is already a link
With Expo Router, your file structure is your link map:
app/
index.tsx → /
products/[id].tsx → /products/42
settings/profile.tsx → /settings/profile
Read params in the screen:
// app/products/[id].tsx
import { useLocalSearchParams } from "expo-router"
export default function Product() {
const { id, ref } = useLocalSearchParams<{ id: string; ref?: string }>()
// myapp://products/42?ref=email → id = "42", ref = "email"
}
New to file-based routing? Start with our Expo Router navigation guide.
1. Custom URL scheme (myapp://)
Add a scheme in app.json:
{
"expo": {
"scheme": "habitly"
}
}
Rebuild your development build, and habitly://products/42 opens the product screen. Schemes are simple but limited: they only work when the app is installed, and they aren't verified, so any app can claim the same scheme. Use them for OAuth redirects, widgets, and internal links.
2. iOS Universal Links (https)
Universal Links open your app from regular https://yourdomain.com/... links, and fall back to your website when the app isn't installed.
In app.json (no https:// prefix):
{
"expo": {
"ios": {
"associatedDomains": ["applinks:habitly.app"]
}
}
}
On your website, serve https://habitly.app/.well-known/apple-app-site-association (no file extension, valid JSON, HTTPS):
{
"applinks": {
"details": [
{
"appIDs": ["ABCDE12345.com.yourcompany.habitly"],
"components": [
{ "/": "/products/*" },
{ "/": "/invite/*" },
{ "/": "/admin/*", "exclude": true }
]
}
]
}
}
ABCDE12345 is your Apple Team ID. With Expo Router web, put the file in public/.well-known/ so it deploys with your site. Keep it under 128KB, and rebuild the app after changes so iOS re-fetches it.
3. Android App Links (https)
In app.json, add a verified intent filter:
{
"expo": {
"android": {
"intentFilters": [
{
"action": "VIEW",
"autoVerify": true,
"data": [{ "scheme": "https", "host": "habitly.app", "pathPrefix": "/products" }],
"category": ["BROWSABLE", "DEFAULT"]
}
]
}
}
}
On your website, serve https://habitly.app/.well-known/assetlinks.json:
[
{
"relation": ["delegate_permission/common.handle_all_urls"],
"target": {
"namespace": "android_app",
"package_name": "com.yourcompany.habitly",
"sha256_cert_fingerprints": ["AB:CD:EF:..."]
}
}
]
Get the fingerprint with:
eas credentials -p android
Copy the SHA256 Fingerprint for your build profile. If you distribute through Google Play with Play App Signing, also add the app signing key fingerprint from Play Console. A missing Play fingerprint is the most common reason App Links work in testing but not in production.
Testing deep links
Custom scheme:
npx uri-scheme open "habitly://products/42" --ios
npx uri-scheme open "habitly://products/42" --android
Android App Links:
adb shell am start -a android.intent.action.VIEW -c android.intent.category.BROWSABLE -d "https://habitly.app/products/42" com.yourcompany.habitly
iOS Universal Links: paste the link into Notes or Messages on a real device and tap it. Typing it into Safari's address bar doesn't trigger Universal Links.
Deep links from push notifications
Send a url in the notification data and navigate when it's tapped:
import { useEffect } from "react"
import * as Notifications from "expo-notifications"
import { router } from "expo-router"
export function useNotificationDeepLinks() {
useEffect(() => {
const sub = Notifications.addNotificationResponseReceivedListener((response) => {
const url = response.notification.request.content.data?.url
if (typeof url === "string") router.push(url)
})
return () => sub.remove()
}, [])
}
Send { "url": "/chats/123" } and the tap opens that chat. Full setup is in our push notifications guide.
Protect private routes
A deep link can point to a screen the user isn't allowed to see yet. Guard it at the layout level and redirect to sign-in. Expo Router's protected routes and our authentication guide show the pattern. After sign-in, send the user back to the original link.
Deep link checklist
schemeset, plusassociatedDomainsandintentFiltersfor https links- AASA and assetlinks.json served over HTTPS from
/.well-known/ - Play App Signing fingerprint included in assetlinks.json
- Every linked screen handles missing or invalid params gracefully
- Auth-gated screens redirect and return after sign-in
- Tested on real devices with the production build
Deep links are also how widgets and Live Activities open the right screen. Our Expo templates give you an Expo Router structure that maps cleanly to shareable URLs.