Admin
Manage users, roles, bans, sessions, and impersonation.
The Admin plugin adds a protected user-management surface to your auth API:
listing and creating users, changing roles, banning, revoking sessions,
impersonating, setting passwords, and permission checks. Access is gated by role
(admin_roles) or by an explicit allowlist of user ids (admin_user_ids).
Installation
Add the plugin to your server
Pass admin() to your Kernia config. admin_user_ids is the bootstrap escape
hatch for your first admin account.
from kernia import KerniaOptions
from kernia.auth import init
from kernia.plugins.admin import AdminOptions, admin
auth = init(KerniaOptions(
database=adapter,
secret=os.environ["KERNIA_SECRET"],
base_url=os.environ["KERNIA_BASE_URL"],
plugins=[
admin(AdminOptions(
default_role="user",
admin_roles=("admin",),
admin_user_ids=(os.environ["BOOTSTRAP_ADMIN_USER_ID"],),
)),
],
))Add the client plugin
Add the admin client plugin:
import { createAuthClient } from "better-auth/client";
import { adminClient } from "better-auth/client/plugins";
export const authClient = createAuthClient({
baseURL: "/api/auth",
plugins: [adminClient()],
});Usage
All calls below require an authenticated admin (a user whose role is in
admin_roles, or whose id is in admin_user_ids).
List users
const { data } = await authClient.admin.listUsers({
query: { limit: 50, offset: 0, searchField: "email", searchValue: "@example.com" },
});Create a user
await authClient.admin.createUser({
email: "user@example.com",
password: "secure-password",
name: "Jane",
role: "user",
});Set a user's role
await authClient.admin.setRole({ userId: "user_123", role: "admin" });Ban and unban
await authClient.admin.banUser({ userId: "user_123", banReason: "abuse", banExpiresIn: 604800 });
await authClient.admin.unbanUser({ userId: "user_123" });Banning revokes the user's sessions and blocks sign-in until the ban lapses or is lifted.
Impersonate a user
await authClient.admin.impersonateUser({ userId: "user_123" });
await authClient.admin.stopImpersonating();impersonateUser issues a session for the target and stamps the session's
impersonatedBy field with your admin id.
Manage sessions and passwords
await authClient.admin.listUserSessions({ userId: "user_123" });
await authClient.admin.revokeUserSession({ sessionToken: "..." });
await authClient.admin.setUserPassword({ userId: "user_123", newPassword: "new-secure-password" });
await authClient.admin.removeUser({ userId: "user_123" });Options
| Option | Type | Default | Description |
|---|---|---|---|
default_role | str | "user" | Role assigned to new users. |
admin_roles | tuple[str, ...] | ("admin",) | Roles allowed to call admin endpoints. Each must exist in roles. |
admin_user_ids | tuple[str, ...] | () | User ids treated as admins regardless of role. |
roles | Mapping[str, Role] | None | default roles | Custom role/permission map from the access-control primitive. |
banned_user_message | str | support message | Message returned when a banned user is rejected. |
default_ban_reason | str | None | None | Reason applied when banReason is omitted. |
default_ban_expires_in | int | None | None | Default ban duration in seconds (None = permanent). |
impersonation_session_duration | int | None | None | Lifetime of an impersonation session in seconds. |
allow_impersonating_admins | bool | False | Allow impersonating other admins. |
Schema
The plugin extends the user and session tables:
rolebannedbanReasonbanExpiresimpersonatedByThe ban check runs as a before-hook on every gated request, so a mid-session ban
takes effect immediately — the banned user is rejected with USER_BANNED on
their next call, not just at sign-in.