Users
User management at /admin/users — ban, impersonate, promote, revoke sessions, delete.
/admin/users (app/admin/users/_components/user-table.tsx) is the user management table.
The list
Columns displayed per user:
- Avatar + name
- Email (with verified badge)
- Role
- Signup date
- Ban status
Filters & search
- Search by name or email
- Filter tabs: All / Admin / Banned
- Sort: Newest / Oldest / Name A-Z
Filter state lives in the URL.
Per-row actions
Each row has a quick Ban / Unban button and a More dropdown with:
- Impersonate — calls
authClient.admin.impersonateUser(). A banner appears during impersonation with a Stop button. - Promote / Demote — toggles
rolebetween"admin"and"user" - Revoke sessions — invalidates all of that user's active sessions
- Delete user — permanent hard delete (double-confirm dialog)
All actions go through confirmation dialogs (see the confirmAction state machine in the component).
Ban flow
The ban dialog accepts an optional reason. On confirm:
user.banned = trueuser.ban_reasonset- All active sessions revoked — the user is signed out everywhere immediately
Banned users see a message on /sign-in with the reason.
Delete flow
Hard delete with cascade — the user row drops, and FK-linked rows in session, account, verification, votes, comments, submissions follow per their schema cascade rules. There is no undo; restore from a DB backup if needed.
For a soft-delete strategy instead, replace the delete with:
await db.update(user)
.set({ email: `deleted-${id}@example.com`, name: "[deleted]", banned: true })
.where(eq(user.id, id))What's not there
Intentionally omitted:
- Bulk actions / multi-select — every action is per-row
- CSV / activity export — no built-in export
- User detail page with activity timeline — only the flat list
All of these are straightforward to add on top of the existing server actions (ban, impersonate, promote, revokeSessions, delete).
Preventing admin lockout
If your only admin bans or deletes themselves, you're locked out. Recover with SQL:
UPDATE "user"
SET banned = false, ban_reason = null, role = 'admin'
WHERE email = '[email protected]';Best practice: always keep at least two admin accounts.