Admin panel
Internal back-office — user/org search, impersonation, and overrides.
The admin panel is an internal, platform-scoped back-office at /admin. Unlike
every other surface it is not org-scoped: a platform admin searches across all
users and orgs, impersonates a user, and applies plan and flag overrides through
the billing and flags seams. Access is gated by a dedicated admin procedure and
bootstrapped with a CLI.
Optional feature
Admin is optional (core: false, requires: ["orgs", "billing", "flags"]).
Its override writes go through billing and flags rather than touching their
data directly, and its usage/audit panels are soft seams.
What it does
- Platform-admin gate —
adminProcedure(server/admin-procedure.ts) layers onprotectedProcedure: unauthenticated →UNAUTHORIZED, signed-in non-staff →FORBIDDEN. Role comes fromctx.user.role;platformRoleSatisfies(role, min)andPLATFORM_ROLE_ORDERleave room for a futuresupporttier. - Search tables —
searchUsers(offset-paginated over the Better Auth admin plugin) andsearchOrgs(cursor-paginated over Prisma), surfaced asAdminUsersTable,AdminOrgsTable, andAdminDashboard(platform counts + recent signups). - Org detail —
admin.orgDetailfeedsAdminOrgDetailPanel: members, subscription, and flag overrides always; usage summary and audit tail only when those features are present. - Impersonation —
admin.impersonatestashes the admin session and starts a target session (Session.impersonatedBy);admin.stopImpersonatingruns onprotectedProcedure(the live session is the impersonated user). TheImpersonationBanneris mounted by the(app)layout behind@feature:adminmarkers. - Overrides —
admin.setPlanOverridewritesSubscription.planKeyOverrideonly through billing'ssetPlanKeyOverride(Stripe untouched);admin.setFlagOverridewrites/clears aFlagOverridethrough flags. Both are audited (admin.plan_override_set/admin.flag_override_set). admin:promoteCLI —promoteToAdmin(db, email)setsUser.role = "admin"directly (idempotent), bootstrapping the first admin that the plugin's own role-setting requires.
Models it adds
Admin adds no new models — it gates fields on two core models, wrapped in
@feature:admin markers:
| Model | Gated fields |
|---|---|
User | role, banned, banReason, banExpires (the Better Auth admin plugin columns). |
Session | impersonatedBy — the id of the admin acting as this user (a plain column, not a relation). |
Subscription.planKeyOverride and FlagOverride are not admin-owned — they
prune with billing and flags. Admin only writes them through those features'
seams. The admin() plugin registration in auth.ts is marker-wrapped to match.
Env it needs
None. Adds the admin:promote script (pnpm admin:promote <email>), a standalone
tsx entry that builds its own Prisma client and bootstraps the first admin.
Composition
requires: ["orgs", "billing", "flags"]— orgs for user/org search; billing forgetSubscriptionSummaryandsetPlanKeyOverride; flags forlistFlagOverrides/setFlagOverride/clearFlagOverride. All are barrel reads, never raw rows.- Soft seams admin consumes (injected, not required): usage's
getUsageSummaryand audit-log'slistAuditLogEntries+ recorder, wired under nested markers increateAdminRouter. Omit a seam and its panel or audit row is silently absent — admin never imports usage or audit-log directly (D-059). - Dependents — none; admin is a leaf.
What pruning removes
Leaving admin out drops features/admin, the /admin route group, the
admin:promote script, the gated User / Session columns, the admin() plugin
registration, and the marked admin wiring (router mount, seam injection, the
ImpersonationBanner). Impersonation disappears entirely. Because admin requires
orgs + billing + flags — and that stack transitively pulls in email and jobs —
admin cannot be added to a build lacking that whole subtree; but since no feature
requires admin, removing admin forces nothing else out.