pealdevDOCS
Back to peal.dev

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

TypeScript
// src/auth.config.ts
plugins: {
  admin: {
    enabled: true,
    defaultRole: "user",
    adminRole: "admin",
  },
},

Client API (must be called from an admin user)

TypeScript
// 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".

Terminal
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.