Admin Panel
User management and system administration in Account UI.
The admin panel provides a user management dashboard for administrators. It's part of the Account UI SPA — when enabled, the /admin route is included in the build.
Enabling Admin
Set auth.admin to true in your account config:
account: {
auth: {
admin: true,
},
},This does two things:
- Sets
enableAdmin: trueon the SPA's runtime config, which unlocks the/adminroutes (they guard themselves and redirect away when the flag is off) - Ensures the Better Auth
adminplugin is enabled on the backend
When auth.admin is false (the default), the admin surface does not exist for your project: /admin redirects away for every user — including operators — and the nav entry is hidden. The compiler always emits the flag explicitly, so an unset value is a hard false.
What It Can Do
Users — every account in the app, independent of organization:
- View all users — Paginated list with search
- Create users — Manually create accounts with email/password
- Ban/unban users — Suspend or reactivate user accounts
- Reset passwords — Generate password reset for any user
- View sessions — See active sessions for any user
Organizations — every tenant, whether or not you belong to it:
- List all organizations — cross-tenant, not just your memberships
- Create an organization — you name the owner; the operator creating it is deliberately not seated (see below)
- Seat, re-role and remove members — manage a tenant's roster without joining it
These run on /auth/v1/admin/*, emitted by Quickback because Better Auth's organization plugin cannot serve them: every one of its endpoints resolves through the caller's membership, and an operator administers organizations it does not belong to.
Users sign themselves up. Self-serve signup stays on — a new account simply has no organization, and lands on a screen saying so until someone invites or seats them. What operators control is organizations, not accounts.
Access Control
The admin panel is restricted to platform roles on the Better Auth user record: appmanager (the control plane) and sysadmin (which adds cross-tenant data access on top). Everyone else is redirected away from /admin.
role: "admin" is not a platform role. The compiler seeds the admin plugin with adminRoles: ["appmanager", "sysadmin"], so a user set to "admin" passed the UI guard and then got 403 from every admin API call — an operator who looked promoted and could not act. Removed in v0.62; migrate those rows to "appmanager".
To grant operator access to a user:
curl -X POST https://api.example.com/auth/v1/admin/set-role \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{ "userId": "user_123", "role": "appmanager" }'Why creating an organization names an owner
Better Auth's organization.create seats whoever calls it. For an operator that is an escalation rather than a convenience: the membership it grants is tenant-data access — under cms.access: "member", access to that organization's records — which is exactly what the appmanager/sysadmin split withholds.
So organization creation asks for an ownerUserId, seats that user, and never seats the caller. Self-serve creation (POST /auth/v1/organization/create) is closed by default for the same reason; a project that wants it sets plugins.organization.allowUserToCreateOrganization: true.
An operator who genuinely wants their own organization creates one naming themselves as owner — explicit, and visible in the audit trail rather than implied by whoever happened to make the call.
Custom Admin Domain
Give the admin panel its own subdomain with adminDomain:
account: {
domain: "auth.example.com",
adminDomain: "admin.example.com",
auth: { password: true, admin: true },
},
trustedOrigins: [
"https://auth.example.com",
"https://admin.example.com",
],The admin domain serves the same Account SPA at root (/). The SPA's client-side router handles showing the admin page at admin.example.com/admin. Authentication cookies are shared across subdomains automatically via cross-subdomain cookie configuration.
CMS Access Control
When both CMS and Account UI are enabled, the admin panel's profile page shows a "Go to CMS" button. You can restrict this to admin users only:
cms: { domain: "cms.example.com", access: "sysadmin" },With access: "sysadmin", only the cross-tenant sysadmin tier reaches the CMS: the link is hidden in Account UI, /api/v1/schema returns 403, and custom_view CRUD is locked to userRole: ["sysadmin"]. Note that an appmanager does not get in — /account/admin (this page) is the appmanager's surface, and the CMS is not. See CMS access gate for the full model.
Related
- Configuration —
auth.adminandadminDomainoptions - Multi-Domain Architecture — Custom domains and hostname routing
- Access Control — Role-based permissions in your API