Skip to main content

How to build custom deep links in an Expo-based mobile app

Deep links are URLs that take users directly to specific content within a mobile app or website, rather than just launching the app's home screen. They work like shortcuts, enabling smoother navigation and improving user experience.

In this guide, you will learn the basics of adding deep links into your Expo-based mobile app.

Deep link is constructed by three parts:

  • Scheme: The URL scheme that identifies the app that should open the URL (example: myapp://). It can also be https or http for non-standard deep links.
  • Host: The domain name of the app that should open the URL (example: web-app.com).
  • Path: The path to the screen that should be opened (example: /product). If the path isn't specified, the user is taken to the home screen of the app.

Deep links can also have params just like web links!

Example:

rnwalletapp://swap?from={token}&to={token}&amount={amount}
note

If you'd like to try a deep link demo you can do by cloning this repo and switching to the branch/deeplink branch:

git clone https://github.com/monad-developers/expo-swap-template.git
git checkout branch/deeplink

Defining the scheme

The first step is to define a scheme; you can do so by editing the app.json file in the Expo project.

app.json
{
"expo": {
"scheme": "myapp" // or your preferred scheme
}
}
warning

Custom schemes like myapp:// are not unique across Android or iOS. If two apps register the same scheme, the system won’t know which one to launch, or it might launch the wrong one. Use something app-specific and hard to accidentally duplicate.

In your app entrypoint (e.g., _layout.tsx or a provider), add logic to:

  • Handle initial deep link
  • Listen for deep link changes

A good practice is to create a DeepLinkHandler and wrap the entire app with it.

Example (in an Expo project using File-based routing):

_layout.tsxapp
12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061
...
// Function to parse the deep link and get the hostname and queryParams
function parseSwapDeeplink(url: string): SwapDeeplinkParams | null {
try {
const { hostname, queryParams } = Linking.parse(url);
if (hostname !== 'swap' || !queryParams) {
return null;
}
return {
from: queryParams.from as string | undefined,
to: queryParams.to as string | undefined,
amount: queryParams.amount as string | undefined,
};
} catch (error) {
console.error('Error parsing deeplink:', error);
return null;
}
}
function DeeplinkHandler({ children }: { children: React.ReactNode }) {
const router = useRouter();
useEffect(() => {
const handleDeeplink = (url: string) => {
// Parse the deep link and get the params (host, path, params etc...)
const params = parseSwapDeeplink(url);
if (params) {
// The example here makes the params globally accessible in the app, however you can use React Context or similar to make the params accessible from anywhere in the app.
(global as any).swapDeeplinkParams = params;
// Based on the path or host you can redirect the user to the respective screen in the app
router.replace('/');
}
};
// Handle initial URL
Linking.getInitialURL().then(url => url && handleDeeplink(url));
// Create an event listener and handle URL changes while app is open
const subscription = Linking.addEventListener('url', event => handleDeeplink(event.url));
// Removes the event listener when the component is destroyed (avoids memory leaks)
return () => subscription.remove();
}, [router]);
return <>{children}</>;
}
export default function Layout() {
...
return (
<DeeplinkHandler>
<App />
</DeeplinkHandler>
);
}

That's it, your app is ready to handle deep links based on the hostname and queryParams you can redirect the user to the respective screens.

Additionally if you make the queryParams accessible globally (via context or some other way) you can prefill input values too!

Example: Prefilling token swap amounts!

Here's a demo of how deep links work in a mobile app:

Testing on iOS simulator

xcrun simctl openurl booted [deeplink]

Example:

xcrun simctl openurl booted "rnwalletapp://swap?from=MON&to=USDC&amount=100"

Testing on Android emulator

adb shell am start -W -a android.intent.action.VIEW -d [deeplink]

Example:

# Important: Use single quotes to wrap the entire command to prevent shell from parsing & symbols
adb shell 'am start -W -a android.intent.action.VIEW -d "rnwalletapp://swap?from=MON&to=USDC&amount=100"'
warning

If you don't use single quotes, the shell will interpret & as a command separator, and only the first parameter will be passed to the app.

Testing on a physical device

You can create a simple HTML page with links.

Example:

<a href="rnwalletapp://swap?from=MON&to=USDC&amount=100">Swap MON to USDC</a>

Try out the demo

If you'd like to try a deep link demo you can do by setting up this repo and switch to the branch/deeplink branch.

git clone https://github.com/monad-developers/expo-swap-template.git
git checkout branch/deeplink

Here are some deep links you can try:

  1. Swap MON to USDC
rnwalletapp://swap?from=MON&to=USDC
  1. Swap 100 MON to USDC
rnwalletapp://swap?from=MON&to=USDC&amount=100
  1. Swap USDC to WMON
rnwalletapp://swap?from=USDC&to=WMON&amount=1000