AUTH STARTER · ADMIN & ROLES
Admin & roles
Ban users, assign roles, list paginated users. The fields are added to the user table automatically.
Enabling the admin plugin adds role, banned, banReason, and banExpires columns to the user table. The plugin exposes management APIs — list, ban, unban, set role, remove. UI is up to you (we don't ship an admin panel — that's a separate template).
Configure
// src/auth.config.ts
plugins: {
admin: {
enabled: true,
defaultRole: "user",
adminRole: "admin",
},
},Client API (must be called from an admin user)
// List users
const { users, total } = await authClient.admin.listUsers({
query: { limit: 20, offset: 0 },
});
// Ban
await authClient.admin.banUser({
userId,
banReason: "Spam",
banExpires: new Date(Date.now() + 1000 * 60 * 60 * 24 * 7), // 7d
});
// Set role
await authClient.admin.setRole({ userId, role: "admin" });
// Remove user
await authClient.admin.removeUser({ userId });Bootstrapping the first admin
There's no "sign up as admin" UI. Your first admin gets the role assigned manually. Easiest path: open Drizzle Studio, find the user row, set role to "admin".
bun run db:studio
# user table → find your row → role = "admin"Banned users still have valid cookies until they expire
When you ban a user, the session cookie cache means they might still pass requireSession for up to 5 minutes. Better Auth handles the auth API side correctly, but your own protected actions should also check session.user.banned before doing anything destructive.
Always re-check role on the server
Reading session.user.role on the CLIENT and showing/hiding admin buttons is fine for UX. But never trust it for the actual action — server actions and route handlers must call requireSession and re-check role from the DB session, not from client state.