šŸ“„ expo/router/advanced/protected

File: protected.md | Updated: 11/15/2025

Source: https://docs.expo.dev/router/advanced/protected

Hide navigation

Search

Ctrl K

Home Guides EAS Reference Learn

Archive Expo Snack Discord and Forums Newsletter

Protected routes

Edit page

Copy page

Learn how to make screens inaccessible to client-side navigation.

Edit page

Copy page


Protected routes are available in SDK 53 and later.

Watch: Using protected routes

Watch: Using protected routes

Overview


Protected screens allow you to prevent users from accessing certain routes using client-side navigation. If a user tries to navigate to a protected screen, or if a screen becomes protected while it is active, they will be redirected to the anchor route (usually the index screen) or the first available screen in the stack.

app

 _layout.tsx

 index.tsx

 about.tsx

 login.tsx``Should only be available while not authenticated

 private

  _layout.tsx``Should only be available while authenticated

  index.tsx

  page.tsx

app/_layout.tsx

Copy

import { Stack } from 'expo-router'; const isLoggedIn = false; export function AppLayout() { return ( <Stack> <Stack.Protected guard={!isLoggedIn}> <Stack.Screen name="login" /> </Stack.Protected> <Stack.Protected guard={isLoggedIn}> <Stack.Screen name="private" /> </Stack.Protected> {/* Expo Router includes all routes by default. Adding Stack.Protected creates exceptions for these screens. */} </Stack> ); }

Show More

In this example, the /private route is inaccessible because the guard is false. When a user attempts to access /private, they are redirected to the anchor route, which is the index screen.

Additionally, if the user is on /private/page and the guard condition changes to false, they will be redirected automatically.

When a screen's guard is changed from true to false, all of its history entries will be removed from the navigation history.

Multiple protected screens


In Expo Router, a screen can only exist in one active route group at a time.

You should only declare a screen only once, in the most appropriate group or stack. If a screen's availability depends on logic, wrap it in a conditional group instead of duplicating the screen.

app/_layout.tsx

Copy

import { Stack } from 'expo-router'; const isLoggedIn = true; const isAdmin = true; export function AppLayout() { return ( <Stack> <Stack.Protected guard={true}> <Stack.Screen name="profile" /> </Stack.Protected> <Stack.Screen name="profile" /> // āŒ Not allowed: duplicate screen </Stack> ); }

Nesting protected screens


Protected screens can be nested to define hierarchical access control logic.

app/_layout.tsx

Copy

import { Stack } from 'expo-router'; const isLoggedIn = true; const isAdmin = true; export function AppLayout() { return ( <Stack> <Stack.Protected guard={isLoggedIn}> <Stack.Protected guard={isAdmin}> <Stack.Screen name="private" /> </Stack.Protected> <Stack.Screen name="about" /> </Stack.Protected> </Stack> ); }

Show More

In this case:

  • /private is only protected if the user is logged in and is an admin.
  • /about is protected to any logged-in user.

Falling back to a specific screen


You can configure the navigator to fall back to a specific screen if access is denied.

app

 _layout.tsx

 index.tsx

 about.tsx

 login.tsx

 private

  _layout.tsx

  index.tsx

  page.tsx

app/_layout.tsx

Copy

import { Stack } from 'expo-router'; const isLoggedIn = false; export function AppLayout() { return ( <Stack> <Stack.Protected guard={isLoggedIn}> <Stack.Screen name="index" /> <Stack.Screen name="private" /> </Stack.Protected> <Stack.Screen name="login" /> </Stack> ); }

In the above example, since the index screen is protected and the guard is false, the router redirects to the first available screen — login.

Tabs and Drawer


Protected routes are also available for Tabs and Drawer navigators.

app/_layout.tsx

Copy

import { Tabs } from 'expo-router'; const isLoggedIn = false; export default function TabLayout() { return ( <Tabs> <Tabs.Screen name="index" options={{ tabBarLabel: 'Home' }} /> <Tabs.Protected guard={isLoggedIn}> <Tabs.Screen name="private" options={{ tabBarLabel: 'Private' }} /> <Tabs.Screen name="profile" options={{ tabBarLabel: 'Profile' }} /> </Tabs.Protected> <Tabs.Protected guard={!isLoggedIn}> <Tabs.Screen name="login" options={{ tabBarLabel: 'Login' }} /> </Tabs.Protected> </Tabs> ); }

Show More

Custom navigators


Protected is also available for custom navigators using the withLayoutContext hook.

Static rendering considerations


Protected screens are evaluated on the client side only. During static site generation, no HTML files are created for protected routes. However, if users know the URLs of these routes, they can still request the corresponding HTML or JavaScript files directly. Protected screens are not a replacement for server-side authentication or access control.