Skip to main content

Deep linking

Thanks to Expo Router's file-system based routing, we get deep linking out of the box. But what does that actually mean?

Because of the constraints we have in defining screens in the app folder, each screen has an exact unambiguous url. This means that Expo Router can (and does) support deep linking out of the box.

So what is deep linking? It is opening a specific page in your app from outside of the app. Apps register to listen to a specific scheme - recall how we set up a scheme in our app.json back when we installed Expo Router? That will be the keyword our app listens to. The scheme isn't guaranteed to be unique, and when multiple apps register to the same scheme, on Android you'll get prompted to choose which app to open, and on iOS the OS just opens one.

Secure deep links

For some more advanced reading on deep links, check out Universal and App Links. With this method, you upload a specific file on your website, and add it as an associated domain to your app. This sets up a more robust deep linking flow.

The scheme looks like this: plantly://. It looks similar to http://, mailto://or tel://? It's because it using a similar system for hooking into the native app with an intent.

In our app, plantly:// will open the home page, but we can specify which screen to link to based on the url! So e.g. we can link to a plants details page with plantly://plants/1.

To open a deep link in your iOS simulator, run:

npx uri-scheme open plantly://plants/1 --ios

For the Android emulator, run:

npx uri-scheme open plantly://plants/1 --android

Read more in the docs.

Testing deep links on real device

This is sure to come up once you get further in your development journey.

On iOS you can type the url into Safari and it will prompt to open the deep link.

The same won't work on Android, but an easy way to get around this is to find a website that supports live editing HTML (like this), add your link to an a tag:

<a href="plantly://plants/1">Click me</a>

and click it on the phone.

Initial route name

You might have noticed something weird going on if we deep link to out plant details page from a completely closed app: there is no way to back to the home page!

We'll need to configure this in the layout file using unstable settings ("unstable" because it does not work with async routes, fine for us to use):

Update: app/(tabs)/(home)/_layout.tsx
@@ -3,6 +3,10 @@ import { Link, Stack } from "expo-router";
import { Pressable } from "react-native";
import { theme } from "../../../theme";

+export const unstable_settings = {
+ initialRouteName: "index",
+};
+
export default function Layout() {
return (
<Stack>

Now when you deep link from a completely closed app, it'll correctly show a back button.

You can also pass in parameters to your deep link. Really you can add anything there, but the convention is to use url syntax.

Add a console.log(params) to the plant details page an open the app with

npx uri-scheme open plantly://plants/1\?query=something --ios

The url param shows up! Neat!

Let's pass in an action so that if we receive ?action=water we'll automatically water the plant.

npx uri-scheme open plantly://plants/1\?action=water --ios

Now add add a useEffect that waters the plans when this query param is passed in:

Update: app/(tabs)/(home)/plants/[plantId].tsx
@@ -19,6 +19,14 @@ export default function PlantDetails() {
);
const navigation = useNavigation();

+ useEffect(() => {
+ if (params.action === "water") {
+ if (typeof plantId === "string") {
+ waterPlant(plantId);
+ }
+ }
+ }, []);
+
useEffect(() => {
navigation.setOptions({
title: plant?.name,

Checkpoint

In-app url params

Though we didn't use any in the app, the same action could be triggered in-app with this action button: yarn

<Link href={{ pathname: "plants/1", params: { action: "water" } }}>
Water me
</Link>