# Build a custom flow for handling user impersonation

> This guide is for users who want to build a custom flow. To use a _prebuilt_ UI, use the [Account Portal pages](https://clerk.com/docs/guides/account-portal/overview.md?sdk=expo) or [prebuilt components](https://clerk.com/docs/expo/reference/components/overview.md).

[Clerk's user impersonation feature](https://clerk.com/docs/guides/users/impersonation.md?sdk=expo) allows you to sign in to your application as one of your users, enabling you to directly reproduce and remedy any issues they're experiencing. It's a helpful feature for customer support and debugging.

This guide will walk you through how to build a custom flow that handles user impersonation.

> You can perform up to **5 user impersonations per month for free**. To increase this limit, refer to the [pricing page](https://clerk.com/pricing){{ target: '_blank' }}.

The following example creates a basic dashboard for impersonating users.

1. ### Protect the dashboard route

   > It is **recommended** that you build impersonation into a dashboard that **only authorized users** can access.

   1. Create the `dashboard/` directory.
   2. In the `dashboard/` directory, create a `_layout.tsx` file with the following code. The [useAuth()](https://clerk.com/docs/expo/reference/hooks/use-auth.md) hook is used to access the user's authentication state. If the user is already signed in, they'll be redirected to the home page. The [<Show >](https://clerk.com/docs/expo/reference/components/control/show.md) component is used to ensure that only users with the `org:dashboard:access` Permission can access it. You can modify the `when: {{ permission: '...' }}` attribute to fit your use case.

   filename: app/dashboard/\_layout.tsx

   ```tsx
   import { Redirect, Stack } from 'expo-router'
   import { Show, useAuth } from '@clerk/expo'
   import { Text } from 'react-native'

   export default function GuestLayout() {
     const { isSignedIn } = useAuth()

     if (!isSignedIn) {
       return <Redirect href={'/'} />
     }

     return (
       <Show
         when={{ permission: 'org:dashboard:access' }}
         fallback={<Text>You don't have the permissions to access the dashboard.</Text>}
       >
         <Stack />
       </Show>
     )
   }
   ```
2. ### Create an API route to generate actor tokens

   To sign in as a different user, you must supply an actor token when creating a session.

   Create the `generateActorToken+api.tsx` file with the following code. This creates an API route that will call Clerk's Backend API [`/actor_tokens`](https://clerk.com/docs/reference/backend-api/tag/actor-tokens/POST/actor_tokens){{ target: '_blank' }} endpoint to create an actor token.

   filename: app/generateActorToken+api.tsx

   ```tsx
   export async function POST(req: Request) {
     const { actorId, userId } = await req.json()

     try {
       // Create an actor token using Clerk's Backend API
       const res = await fetch('https://api.clerk.com/v1/actor_tokens', {
         method: 'POST',
         headers: {
           Authorization: `Bearer ${process.env.CLERK_SECRET_KEY}`,
           'Content-type': 'application/json',
         },
         body: JSON.stringify({
           user_id: userId,
           actor: {
             sub: actorId,
           },
         }),
       })

       const data = await res.json()

       return Response.json(data)
     } catch (err) {
       return Response.json({ error: 'Failed to generate actor token' }, { status: 500 })
     }
   }
   ```
3. ### Create a hook to get users

   To impersonate a user, you need a list of your application's users to be able to select one for impersonation.

   1. Create the `hooks/` directory.
   2. In the `hooks/` directory, create the `useUsers.tsx` file with the following code. This creates a hook that will fetch the list of your application's users.

   filename: app/hooks/useUsers.tsx

   ```tsx
   import { UserJSON } from '@clerk/shared/types'
   import { useEffect, useState } from 'react'

   type UseUsersReturn = {
     users: UserJSON[] | null
     isLoading: boolean
     error: Error | null
   }

   /**
    * Returns a list of users for the application.
    *
    * Until the users are fetched, `isLoading` will be set to `true`.
    *
    * @example
    *
    * import { useUsers } from '@/app/hooks/useUsers';
    *
    * function Hello() {
    *   const { users, isLoading, error } = useUsers();
    *   if(isLoading) {
    *     return <div>Loading...</div>;
    *   }
    *   return <div>Users: {users?.map((user) => user.firstName).join(', ')}</div>
    * }
    */
   export default function useUsers(): UseUsersReturn {
     const [users, setUsers] = useState<UserJSON[] | null>(null)
     const [isLoading, setIsLoading] = useState(true)
     const [error, setError] = useState<Error | null>(null)

     useEffect(() => {
       const getUsers = async () => {
         try {
           const res = await fetch('/getUsers')
           if (!res.ok) {
             throw new Error('Failed to fetch users')
           }
           const data = await res.json()
           setUsers(data)
         } catch (err) {
           setError(err instanceof Error ? err : new Error('Unknown error'))
         } finally {
           setIsLoading(false)
         }
       }

       getUsers()
     }, []) // Remove users from dependency array to prevent infinite loop

     return { users, isLoading, error }
   }
   ```
4. ### Create the dashboard UI

   1. In the `dashboard/` directory, create the `index.tsx` file with the following code. This creates the UI for the dashboard, which displays a list of users and allows you to impersonate one.

   filename: app/dashboard/index.tsx

   ```tsx
   import React, { useState } from 'react'
   import { Button, Text, View } from 'react-native'
   import { useRouter } from 'expo-router'
   import { useUser, useSignIn } from '@clerk/expo'
   import useUsers from '../hooks/useUsers'

   export default function Dashboard() {
     const [error, setError] = useState<string | null>(null)

     const { isLoaded, signIn, setActive } = useSignIn()
     const { isSignedIn, user } = useUser()
     const router = useRouter()
     const { users, isLoading } = useUsers()

     if (!isSignedIn) {
       // Handle signed out state
       return null
     }

     // Create an actor token for the impersonation
     async function createActorToken(actorId: string, userId: string) {
       setError(null)

       try {
         const res = await fetch('/generateActorToken', {
           method: 'POST',
           body: JSON.stringify({
             actorId,
             userId,
           }),
         })

         const data = await res.json()

         if (data.errors) {
           setError(data.errors[0].long_message)
           return null
         }

         return data.token
       } catch (err) {
         setError('Failed to generate actor token')
         return null
       }
     }

     // Handle "Impersonate" button click
     async function impersonateUser(actorId: string, userId: string) {
       setError(null)
       if (!isLoaded) return

       // Calls your /generateActorToken API route
       const actorToken = await createActorToken(actorId, userId)

       // Sign in as the impersonated user
       if (actorToken) {
         try {
           const { createdSessionId } = await signIn.create({
             strategy: 'ticket',
             ticket: actorToken,
           })

           await setActive({ session: createdSessionId })

           router.push('/')
         } catch (err) {
           setError('Failed to impersonate user')
           // See https://clerk.com/docs/guides/development/custom-flows/error-handling
           // for more info on error handling
           console.error(JSON.stringify(err, null, 2))
         }
       }
     }

     return (
       <View>
         <Text style={{ fontSize: 24, fontWeight: 'bold', padding: 10 }}>
           Welcome to the dashboard, {user?.firstName}!
         </Text>
         <Text style={{ fontSize: 16, padding: 10 }}>Your user ID is {user?.id}</Text>

         {isLoading && <Text>Loading your users...</Text>}

         {!isLoading && users && (
           <View style={{ padding: 10 }}>
             <View style={{ flexDirection: 'row', padding: 5, borderBottomWidth: 1 }}>
               <Text style={{ flex: 1, fontWeight: 'bold' }}>User ID</Text>
               <Text style={{ flex: 1, fontWeight: 'bold' }}>Email ID</Text>
               <Text style={{ flex: 1, fontWeight: 'bold' }}>First Name</Text>
               <Text style={{ flex: 1, fontWeight: 'bold' }}>Actions</Text>
             </View>
             {users.map((userFromList) => {
               const primaryEmail = userFromList.email_addresses?.find(
                 (email) => email.id === userFromList.primary_email_address_id,
               )
               return (
                 <View key={userFromList.id} style={{ flexDirection: 'row', padding: 5 }}>
                   <Text style={{ flex: 1 }}>{userFromList.id}</Text>
                   <Text style={{ flex: 1 }}>{primaryEmail?.id || 'N/A'}</Text>
                   <Text style={{ flex: 1 }}>{userFromList.first_name || 'N/A'}</Text>
                   <View style={{ flex: 1 }}>
                     {/* Don't allow impersonation of yourself */}
                     {userFromList.id !== user.id ? (
                       <Button
                         title="Impersonate"
                         onPress={async () => await impersonateUser(user.id, userFromList.id)}
                       />
                     ) : (
                       <Text>You cannot impersonate yourself</Text>
                     )}
                   </View>
                 </View>
               )
             })}
           </View>
         )}

         {error && (
           <View style={{ padding: 10, backgroundColor: '#ffebee' }}>
             <Text style={{ color: '#c62828' }}>{error}</Text>
           </View>
         )}
       </View>
     )
   }
   ```

---

## Sitemap

[Overview of all docs pages](https://clerk.com/docs/llms.txt)
