Overview
The admin panel at /admin — sections, access control, and what's there.
Everything administrative lives under /admin, gated by user.role = 'admin'.
Access control
lib/admin.ts exposes requireAdmin():
export async function requireAdmin() {
const session = await auth.api.getSession({ headers: await headers() })
if (!session || session.user.role !== "admin") throw new Error("Unauthorized")
return session
}The admin layout (app/admin/layout.tsx) calls this and redirects non-admins to / silently (no 403 page). The layout is force-dynamic to prevent caching bypass.
Promoting a user
Three ways, in order of preference:
1. CLI helper (recommended for the first admin):
bun run promote-admin [email protected]Sets role = 'admin' and revokes the user's sessions so the change takes effect on their next sign-in.
2. Drizzle Studio (visual):
bun run db:studioOpen the user table, click the row, change role to admin, save.
3. From /admin/users once at least one admin exists — click More → Promote on the target user.
After any of these, the user must sign out and sign back in for the change to take effect (Better Auth caches the role in a 5-min cookie).
Sections
Nine sections under app/admin/:
| Section | URL | Purpose |
|---|---|---|
| Dashboard | /admin | Entry page |
| Content | /admin/content | Calendar view of current batch, last batch, upcoming products |
| Submissions | /admin/submissions | Moderation queue — accept / reject / request revision |
| Products | /admin/products | Published products — browse, edit, sort by votes / name / date |
| Users | /admin/users | Ban / promote / impersonate / revoke sessions / delete |
| Comments | /admin/comments | Moderation — approve / reject / delete |
| Blog | /admin/blog | Post list, create, edit, publish |
| Sponsors | /admin/sponsors | Active + upcoming bookings, revenue |
| Settings | /admin/settings | 28 configurable settings (see the Settings page) |
| Template Grants | /admin/template-grants | GitHub access grants for the template sale flow (sales site only — excluded from customer distribution) |
What's not in the admin panel
All of the following are explicitly absent and will need to be added if you want them:
- Keyboard shortcuts — no vim-like bindings on any table
- Multi-select / bulk actions — every action is per-row
- CSV / activity export — no built-in export endpoints
- Analytics charts / metrics page — use Plausible or PostHog externally
- Audit log — actions aren't logged to a table
If you need these, they're straightforward to add on top of the existing layout.
Session and impersonation
Admins can impersonate users from /admin/users. Implementation uses Better Auth's admin plugin; the session's impersonated_by column tracks the original admin.
Revalidation
Most admin mutations (accept submission, update settings, etc.) call revalidatePath() for the affected public pages, so changes go live without manual refresh elsewhere.