skip to content

In React Native, how does Linking.openURL open the mail app, the dialer or a browser from a password-reset screen, and when does it fail?

level: juniorimportance: must knowfreq 60%

answer

  1. the scheme picks the app
  2. mailto:, tel:, sms:, https:
  3. a promise that can reject
  4. web URLs need the protocol
  5. tel: fails in the iOS simulator

basics

~20 s

React Native's Linking.openURL hands a URL to the operating system, which opens the app registered for its scheme: mailto: for mail, tel: for the dialer, https: for the browser. Its promise rejects when nothing can open the URL.

solid answer

~40 s

`Linking` from `react-native` is the core module for outgoing and incoming links. On a password-reset screen, "Contact support" can call `Linking.openURL('mailto:[email protected]')` and "Call us" `Linking.openURL('tel:+15550100')`; the operating system picks the app registered for that scheme. `openURL` returns a promise: it resolves when the URL was opened, and rejects when no installed app can handle it or the user cancels a confirmation dialog, so the call belongs in `try`/`catch` with a fallback, such as showing the address to copy. Web URLs need their protocol — `'www.example.com'` without `https://` is not a URL any browser is registered for. And `tel:` does not work in the iOS simulator, which has no dialer, so test it on a device.

code

tsx · 31 lines
tsx
import * as React from 'react';
import { Alert, Button, Linking, View } from 'react-native';

const SUPPORT_EMAIL = '[email protected]';

async function openOrExplain(url: string, fallbackMessage: string) {
  try {
    await Linking.openURL(url);
  } catch {
    Alert.alert('Could not open this link', fallbackMessage);
  }
}

export function ResetHelp() {
  const subject = encodeURIComponent('Password reset help');

  return (
    <View>
      <Button
        title="Email support"
        onPress={() =>
          openOrExplain(`mailto:${SUPPORT_EMAIL}?subject=${subject}`, `Write to ${SUPPORT_EMAIL}.`)
        }
      />
      <Button
        title="Reset on the web"
        onPress={() => openOrExplain('https://example.com/reset', 'Visit example.com/reset.')}
      />
    </View>
  );
}

go deeper

for a junior

Recall Linking.openURL with mailto:, tel:, sms: and https:, and that it returns a promise you should await and catch.

for a middle

Explain scheme-based dispatch, the rejection cases, why a scheme-less web URL fails, and when a canOpenURL check adds nothing.

for a senior

Design fallbacks for missing handlers, encode user-supplied parts of the URL, and test on devices where simulators lack the target apps.

for a principal

Decide which flows may leave the app for another app or the browser, and how that affects support, security and conversion.

## What Linking.openURL does `Linking` is React Native's core module for **links in both directions**: opening URLs in other apps, and receiving URLs that open your app. For the outgoing direction, `Linking.openURL(url)` passes the URL to the operating system, which looks at the URL's **scheme** — the part before the colon — and opens the app registered for it. On a password-reset screen, the buttons around the form are typical uses: - "Contact support" → `mailto:[email protected]` opens the mail composer. - "Call us" → `tel:+15550100` opens the dialer. - "Text us" → `sms:+15550100` opens the messaging app. - "Reset on the web instead" → `https://example.com/reset` opens the browser. React Native's documentation lists `mailto`, `tel`, `sms` and `https`/`http` as built-in schemes available on both iOS and Android. Other apps' **custom schemes** work the same way, if that app is installed. ## The promise and its failure cases `openURL` returns a `Promise`. According to React Native's documentation, it resolves if the URL opened (or the user confirmed an open dialog) and **rejects** if the user cancels the dialog or no installed app is registered for the URL. | Situation | Result | |---|---| | A handler exists and the URL opens | resolves | | No installed app handles the scheme | rejects | | Web URL without a protocol, e.g. `www.example.com` | rejects: no app handles a scheme-less URL | | `tel:` in the iOS simulator | does not open a dialer — the simulator has none; React Native logs a warning | | Empty string | throws immediately with an "Invalid URL" error | On Android, React Native starts a `VIEW` intent for the URL and rejects with "Could not open URL" if starting it fails. On iOS it asks the system to open the URL and rejects with "Unable to open URL" if the system reports failure. ## Writing the call properly 1. Build the URL with its scheme: `mailto:` plus an address, `tel:` plus a number, `https://` for the web. 2. Encode user-provided parts, for example with `encodeURIComponent` on a `subject` query value. 3. `await` the call inside `try`/`catch`. 4. In `catch`, offer a fallback: show the address or number so the user can copy it, or open the web version. Checking first with `Linking.canOpenURL` is optional and has platform-specific configuration requirements of its own; for built-in schemes, attempting `openURL` and handling the rejection is usually simpler. ## What openURL is not - It is **not** an in-app browser. `https://` URLs leave the app and open the system browser; showing web content inside the app needs a web view or a browser-session library. - It is **not** how your app receives links. Incoming URLs arrive through `Linking.getInitialURL()` at launch and the `'url'` event while running. - It does **not** register anything. Opening another app's custom scheme needs nothing in your app's manifest, although *querying* whether such an app is installed can. ## In Expo projects `expo-linking` exposes the same `Linking.openURL` for outgoing links, and Expo Router's `Link` component can open an external `https` URL too; the scheme-based dispatch and the failure cases are the same, because both end in the platform's open-URL call. Querying whether another app is installed is where configuration starts to differ between bare and Expo projects. ## Testing Simulators and emulators often lack the target apps: the iOS simulator has no dialer and may have no mail account set up. Test `tel:` and `mailto:` on real devices, and test the `catch` path deliberately with a scheme no app handles. ## What a strong answer shows It names the scheme-based dispatch, the common schemes, the promise that rejects when nothing can handle the URL, the missing-protocol mistake and the simulator caveat, and it wraps the call in error handling with a useful fallback.

  • Why does Linking.openURL('www.example.com') fail while 'https://www.example.com' works?
    The operating system chooses an app by the URL's scheme. `www.example.com` has no scheme, so no app is registered for it and the promise rejects. React Native's documentation says web URLs must include `http://` or `https://`.
  • How would you open a link inside the app instead of the system browser?
    `Linking.openURL` always hands the URL to another app. Showing a page inside the app needs a web view component or an in-app browser session library; that is a different API with its own trade-offs, such as cookie sharing with the system browser.

saying these in an interview costs you the question

  • Linking.openURL('www.example.com') opens the browser without a protocol.
  • openURL never fails, so there is no need to await it or catch errors.
  • tel: links can be tested fully in the iOS simulator.
  • openURL opens https links inside the app in a web view.
  • Opening mailto: requires registering the scheme in your own Info.plist.