BETTER-AUTH. UI
Mutations

Mutations

TanStack Query primitives for every Better Auth write endpoint.

Every mutation exposes a hook and an options factory. The factory's .mutationKey is the canonical key.

import { useSignInEmail, signInEmailOptions } from "@better-auth-ui/react"

Use the hook in components. Use the factory anywhere TanStack Query takes a mutationOptions object — useMutation, useIsMutating, or a global MutationCache observer.

Error handling

Every mutation wires throw: true into fetchOptions, so the promise rejects with a BetterFetchError on failure instead of resolving with { error }. This means you can rely on error, isError, throwOnError, and standard onError handlers from useMutation.

const { authClient } = useAuth()
const { mutate, error } = useSignInEmail(authClient, {
  onError: (err) => toast.error(err.message)
})

Cache side effects

Hooks that change auth state update the cache for you. Override onSuccess in the hook options and it runs after the built-in side effect:

  • useSignInEmail / useSignInUsername / useSignInPasskey / useSignUpEmail — reset the session query so the new session is refetched. (useSignInSocial and useSignInMagicLink redirect instead, so they leave the cache alone.)
  • useSignOut — remove every ["auth", ...] query.
  • useUpdateUser — optimistically merge the update into the cached session, then refetch.
  • useSetActiveSession — optimistically swap the cached session, scroll to top, and refetch both the session and device-session queries.
  • useChangeEmail — refetch the session.
  • useAddPasskey / useDeletePasskey — refetch the passkey list.
  • useRevokeSession — refetch the sessions list.
  • useRevokeMultiSession — refetch the device sessions list.
  • useUnlinkAccount — refetch the linked accounts list.

Tracking mutation state globally

All mutation keys are prefixed with "auth" and exposed through the shared authMutationKeys factory in @better-auth-ui/core. Use it instead of inlining tuples so call sites share the same source of truth as the mutation factories:

import { authMutationKeys } from "@better-auth-ui/core"
import { useIsMutating } from "@tanstack/react-query"

const authPending = useIsMutating({ mutationKey: authMutationKeys.all })
const signInPending = useIsMutating({ mutationKey: authMutationKeys.signIn.all })
const emailSignInPending = useIsMutating({
  mutationKey: authMutationKeys.signIn.email
})

Each grouping (signIn, signUp, passkey, multiSession) exposes an all prefix so you can match a whole feature at once.

Match inside a MutationCache observer for global toasts or analytics:

import { authMutationKeys } from "@better-auth-ui/core"

new MutationCache({
  onError: (error, _vars, _ctx, mutation) => {
    if (mutation.options.mutationKey?.[0] === authMutationKeys.all[0]) {
      toast.error(error.message)
    }
  }
})

Escape hatch

For write-style endpoints without a purpose-built hook, use useAuthMutation (or its options factory authMutationOptions). Read-style endpoints should use useAuthQuery instead.

import { useAuth, useAuthMutation } from "@better-auth-ui/react"

const { authClient } = useAuth()
const { mutate } = useAuthMutation(
  authClient.emailOtp.sendVerificationOtp,
  ["auth", "emailOtp", "sendVerificationOtp"]
)

mutate({ email: "[email protected]", type: "sign-in" })

Variables are inferred from authFn's parameter — required params reject mutate() at the type level, optional-param methods (e.g. signOut) allow it. throw: true is wired into fetchOptions so onError and error get a BetterFetchError.

For shared mutation registration (useIsMutating, a global MutationCache observer, manual useMutation), use the factory directly:

authMutationOptions(authClient.emailOtp.sendVerificationOtp, [
  "auth",
  "emailOtp",
  "sendVerificationOtp"
])

For endpoints that already have a key in authMutationKeys, prefer it over an inline tuple so cache observers and useIsMutating checks line up.

Available mutations

Auth

Settings

Organization

Last updated on

On this page