Build a custom sign-out flow
Clerk's <UserButton /> and <SignOutButton /> components provide an out-of-the-box solution for signing out users. However, if you're building a custom solution, you can use the function to handle the sign-out process.
The signOut()
function signs a user out of all sessions in a multi-session application, or only the current session in a single-session context. You can also specify a specific session to sign out by passing the sessionId
parameter.
The useClerk() hook is used to access the signOut()
function, which is called when the user clicks the sign-out button.
This example is written for Next.js App Router but can be adapted for any React-based framework.
'use client'
import { useClerk } from '@clerk/nextjs'
export const SignOutButton = () => {
const { signOut } = useClerk()
return (
// Clicking this button signs out a user
// and redirects them to the home page "/".
<button onClick={() => signOut({ redirectUrl: '/' })}>Sign out</button>
)
}
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="/clerk.svg" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Clerk + JavaScript App</title>
</head>
<body>
<div id="app"></div>
<button id="sign-out">Sign out</button>
<script type="module" src="main.js" async crossorigin="anonymous"></script>
</body>
</html>
import { Clerk } from '@clerk/clerk-js'
const pubKey = import.meta.env.VITE_CLERK_PUBLISHABLE_KEY
const clerk = new Clerk(pubKey)
await clerk.load()
if (clerk.isSignedIn) {
// Attach signOut function to the sign-out button
document.getElementById('sign-out').addEventListener('click', async () => {
await clerk.signOut()
// Optional: refresh page after sign-out
window.location.reload()
})
}
The useClerk() hook is used to access the signOut()
function, which is called when the user clicks the "Sign out" button.
import { useClerk } from '@clerk/clerk-expo'
import { useRouter } from 'expo-router'
import { Text, TouchableOpacity } from 'react-native'
export const SignOutButton = () => {
// Use `useClerk()` to access the `signOut()` function
const { signOut } = useClerk()
const router = useRouter()
const handleSignOut = async () => {
try {
await signOut()
// Redirect to your desired page
router.replace('/')
} catch (err) {
// See https://clerk.com/docs/custom-flows/error-handling
// for more info on error handling
console.error(JSON.stringify(err, null, 2))
}
}
return (
<TouchableOpacity onPress={handleSignOut}>
<Text>Sign out</Text>
</TouchableOpacity>
)
}
import SwiftUI
import Clerk
struct SignOutView: View {
@Environment(Clerk.self) private var clerk
var body: some View {
if let session = clerk.session {
Text("Active Session: \(session.id)")
Button("Sign out") {
Task { await signOut() }
}
} else {
Text("You are signed out")
}
}
}
extension SignOutView {
func signOut() async {
do {
try await clerk.signOut()
} catch {
// See https://clerk.com/docs/custom-flows/error-handling
// for more info on error handling.
dump(error)
}
}
}
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import com.clerk.api.Clerk
import com.clerk.api.network.serialization.onFailure
import com.clerk.api.network.serialization.onSuccess
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.launch
class MainViewModel : ViewModel() {
private val _uiState = MutableStateFlow<UiState>(UiState.Loading)
val uiState = _uiState.asStateFlow()
init {
combine(Clerk.isInitialized, Clerk.userFlow) { isInitialized, user ->
_uiState.value =
when {
!isInitialized -> UiState.Loading
user != null -> UiState.SignedIn
else -> UiState.SignedOut
}
}
.launchIn(viewModelScope)
}
fun signOut() {
viewModelScope.launch {
Clerk.signOut()
.onSuccess { _uiState.value = UiState.SignedOut }
.onFailure {
// See https://clerk.com/docs/custom-flows/error-handling
// for more info on error handling
}
}
}
sealed interface UiState {
data object SignedIn : UiState
data object SignedOut : UiState
data object Loading : UiState
}
}
import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.activity.enableEdgeToEdge
import androidx.activity.viewModels
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.material3.Button
import androidx.compose.material3.CircularProgressIndicator
import androidx.compose.material3.Text
import androidx.compose.runtime.getValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.lifecycle.compose.collectAsStateWithLifecycle
class MainActivity : ComponentActivity() {
val viewModel: MainViewModel by viewModels()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
enableEdgeToEdge()
setContent {
val state by viewModel.uiState.collectAsStateWithLifecycle()
Box(
modifier = Modifier.fillMaxSize(),
contentAlignment = Alignment.Center
) {
when (state) {
MainViewModel.UiState.Loading -> {
CircularProgressIndicator()
}
MainViewModel.UiState.SignedIn -> {
Button(onClick = { viewModel.signOut() }) {
Text("Sign out")
}
}
MainViewModel.UiState.SignedOut -> {
// Signed out content
}
}
}
}
}
}
Feedback
Last updated on