Expo Application Services
API Reference


expo-web-browser provides access to the system's web browser and supports handling redirects. On iOS, it uses SFSafariViewController or SFAuthenticationSession, depending on the method you call, and on Android it uses ChromeCustomTabs. As of iOS 11, SFSafariViewController no longer shares cookies with Safari, so if you are using WebBrowser for authentication you will want to use WebBrowser.openAuthSessionAsync, and if you just want to open a webpage (such as your app privacy policy), then use WebBrowser.openBrowserAsync.

Platform Compatibility

Android DeviceAndroid EmulatoriOS DeviceiOS SimulatorWeb


→ expo install expo-web-browser

If you're installing this in a bare React Native app, you should also follow these additional installation instructions.

Basic WebBrowser usage
import React, { useState } from 'react';
import { Button, Text, View, StyleSheet } from 'react-native';
import * as WebBrowser from 'expo-web-browser';
%%placeholder-start%%%%placeholder-end%%import Constants from 'expo-constants';

export default function App() {
  const [result, setResult] = useState(null);

  const _handlePressButtonAsync = async () => {
    let result = await WebBrowser.openBrowserAsync('');
  return (
    <View style={styles.container}>
      <Button title="Open WebBrowser" onPress={_handlePressButtonAsync} />
      <Text>{result && JSON.stringify(result)}</Text>

%%placeholder-start%%const styles = StyleSheet.create({ ... }); %%placeholder-end%%const styles = StyleSheet.create({
  container: {
    flex: 1,
    alignItems: 'center',
    justifyContent: 'center',
    paddingTop: Constants.statusBarHeight,
    backgroundColor: '#ecf0f1',

If you use the WebBrowser window for authentication or another use case where you want to pass information back into your app through a deep link, add a handler with Linking.addEventListener before opening the browser. When the listener fires, you should call dismissBrowser -- it will not automatically dismiss when a deep link is handled. Aside from that, redirects from WebBrowser work the same as other deep links. Read more about it in the Linking guide.

import * as WebBrowser from 'expo-web-browser';

stringOptional - Package of browser to be cooled. If not set, preferred browser will be used.

Android only. This methods removes all bindings to services created by warmUpAsync or mayInitWithUrlAsync. You should call this method once you don't need them to avoid potential memory leaks. However, those binding would be cleared once your application is destroyed, which might be sufficient in most cases.

  • Undo-iconPromise<WebBrowserCoolDownResult>

The promise which fulfils with { package: string } when cooling is performed, or an empty object when there was no connection to be dismissed.

iOS only. Dismisses the presented web browser.

  • Undo-iconvoid

The promise which fulfils with { type: 'dismiss' } object.

Android only. Returns a list of applications package names supporting Custom Tabs, Custom Tabs service, user chosen and preferred one. This may not be fully reliable, since it uses PackageManager.getResolvingActivities under the hood. (For example, some browsers might not be present in browserPackages list once another browser is set to default.)

The promise which fulfils with WebBrowserCustomTabsResults object.

urlstringThe url of page that is likely to be loaded first when opening browser.
stringOptional - Package of browser to be informed. If not set, preferred browser will be used.

Android only. This method initiates (if needed) CustomTabsSession and calls its mayLaunchUrl method for browser specified by the package.

A promise which fulfils with { package: string } object.


Web only. Possibly completes an authentication session on web in a window popup. The method should be invoked on the page that the window redirects to.

Returns an object with message about why the redirect failed or succeeded:

If type is set to failed, the reason depends on the message:

  • Not supported on this platform: If the platform doesn't support this method (iOS, Android).
  • Cannot use expo-web-browser in a non-browser environment: If the code was executed in an SSR or node environment.
  • No auth session is currently in progress: (the cached state wasn't found in local storage). This can happen if the window redirects to an origin (website) that is different to the initial website origin. If this happens in development, it may be because the auth started on localhost and finished on your computer port (Ex: 128.0.0.*). This is controlled by the redirectUrl and returnUrl.
  • Current URL "<URL>" and original redirect URL "<URL>" do not match: This can occur when the redirect URL doesn't match what was initial defined as the returnUrl. You can skip this test in development by passing { skipRedirectCheck: true } to the function.

If type is set to success, the parent window will attempt to close the child window immediately.

If the error ERR_WEB_BROWSER_REDIRECT was thrown, it may mean that the parent window was reloaded before the auth was completed. In this case you'll need to close the child window manually.

urlstringThe url to open in the web browser. This should be a login page.
redirectUrlstringOptional - The url to deep link back into your app. By default, this will be Constants.linkingUrl.
WebBrowserOpenOptionsOptional - An object with the same keys as WebBrowserOpenOptions. If there is no native AuthSession implementation available (which is the case on Android) these params will be used in the browser polyfill. If there is a native AuthSession implementation, these params will be ignored.
Default: {}

On iOS:

Opens the url with Safari in a modal using SFAuthenticationSession on iOS 11 and greater, and falling back on a SFSafariViewController. The user will be asked whether to allow the app to authenticate using the given url.

On Android:

This will be done using a "custom Chrome tabs" browser, AppState, and Linking APIs.

On web:


This API can only be used in a secure environment (https). You can use expo start:web --https to test this. Otherwise, an error with code ERR_WEB_BROWSER_CRYPTO will be thrown. This will use the browser's API.

  • Desktop: This will create a new web popup window in the browser that can be closed later using WebBrowser.maybeCompleteAuthSession().
  • Mobile: This will open a new tab in the browser which can be closed using WebBrowser.maybeCompleteAuthSession().

How this works on web:

  • A crypto state will be created for verifying the redirect.
    • This means you need to run with expo start:web --https
  • The state will be added to the window's localstorage. This ensures that auth cannot complete unless it's done from a page running with the same origin as it was started. Ex: if openAuthSessionAsync is invoked on https://localhost:19006, then maybeCompleteAuthSession must be invoked on a page hosted from the origin https://localhost:19006. Using a different website, or even a different host like https://128.0.0.*:19006 for example will not work.
  • A timer will be started to check for every 1000 milliseconds (1 second) to detect if the window has been closed by the user. If this happens then a promise will resolve with { type: 'dismiss' }.

On mobile web, Chrome and Safari will block any call to which takes too long to fire after a user interaction. This method must be invoked immediately after a user interaction. If the event is blocked, an error with code ERR_WEB_BROWSER_BLOCKED will be thrown.

  • If the user does not permit the application to authenticate with the given url, the Promise fulfills with { type: 'cancel' } object.
  • If the user closed the web browser, the Promise fulfills with { type: 'cancel' } object.
  • If the browser is closed using dismissBrowser, the Promise fulfills with { type: 'dismiss' } object.

urlstringThe url to open in the web browser.
WebBrowserOpenOptionsA dictionary of key-value pairs.
Default: {}

Opens the url with Safari in a modal on iOS using SFSafariViewController, and Chrome in a new custom tab on Android. On iOS, the modal Safari will not share cookies with the system Safari. If you need this, use openAuthSessionAsync.

The promise behaves differently based on the platform. On Android promise resolves with {type: 'opened'} if we were able to open browser. On iOS:

  • If the user closed the web browser, the Promise resolves with { type: 'cancel' }.
  • If the browser is closed using dismissBrowser, the Promise resolves with { type: 'dismiss' }.

stringOptional - Package of browser to be warmed up. If not set, preferred browser will be warmed.

Android only. This method calls warmUp method on CustomTabsClient for specified package.

A promise which fulfils with { package: string } object.

booleanAttempt to close the window without checking to see if the auth redirect matches the cached redirect URL.

messagestringAdditional description or reasoning of the result.
type'success' | 'failed'Type of the result.

  • ServiceActionResult

browserPackagesstring[]All packages recognized by PackageManager as capable of handling Custom Tabs. Empty array means there is no supporting browsers on device.
stringDefault package chosen by user, null if there is no such packages. Also null usually means, that user will be prompted to choose from available packages.
stringPackage preferred by CustomTabsClient to be used to handle Custom Tabs. It favors browser chosen by user as default, as long as it is present on both browserPackages and servicePackages lists. Only such browsers are considered as fully supporting Custom Tabs. It might be null when there is no such browser installed or when default browser is not in servicePackages list.
servicePackagesstring[]All packages recognized by PackageManager as capable of handling Custom Tabs Service. This service is used by warmUpAsync, mayInitWithUrlAsync and coolDownAsync.

string(Android only). Package name of a browser to be used to handle Custom Tabs. List of available packages is to be queried by getCustomTabsSupportingBrowsers method.
string(iOS only) Tint color for controls in SKSafariViewController in #AARRGGBB or #RRGGBB format.
boolean(Android only) A boolean determining whether the browser should open in a new task or in the same task as your app.
Default: `true`
'done' | 'close' | 'cancel'(iOS only) The style of the dismiss button. Should be one of: done, close, or cancel.
booleanA boolean determining whether the toolbar should be hiding when a user scrolls the website.
boolean(Android only) A boolean determining whether a default share item should be added to the menu.
boolean(iOS only) A boolean determining whether Safari should enter Reader mode, if it is available.
string(Android only) Color of the secondary toolbar in either #AARRGGBB or #RRGGBB format.
boolean(Android only) A boolean determining whether browsed website should be shown as separate entry in Android recents/multitasking view. Requires createTask to be true (default).
Default: `false`
boolean(Android only) A boolean determining whether the browser should show the title of website on the toolbar.
stringColor of the toolbar in either #AARRGGBB or #RRGGBB format.
string | WebBrowserWindowFeatures(Web only) Features to use with
string(Web only) Name to assign to the popup window.

type'success'Type of the result.

typeWebBrowserResultTypeType of the result.

  • ServiceActionResult

  • Record<string, number | boolean | string>

WebBrowserResultType.CANCEL = "cancel"

iOS only.

WebBrowserResultType.DISMISS = "dismiss"

iOS only.

WebBrowserResultType.LOCKED = "locked"
WebBrowserResultType.OPENED = "opened"

Android only.

Web only: The window cannot complete the redirect request because the invoking window doesn't have a reference to it's parent. This can happen if the parent window was reloaded.

Web only: The popup window was blocked by the browser or failed to open. This can happen in mobile browsers when the method was invoked too long after a user input was fired.
Mobile browsers do this to prevent malicious websites from opening many unwanted popups on mobile.
You're method can still run in an async function but there cannot be any long running tasks before it. You can use hooks to disable user-inputs until any other processes have finished loading.

Web only: The current environment doesn't support crypto. Ensure you are running from a secure origin (https).
When using Expo CLI you can run expo start:web --https or expo start --web --https to open your web page in a secure development environment.