Admin app
The template ships an admin app at apps/admin, a third surface next to apps/frontend and apps/backend, built for
operators (you and your team), not your end users. Out of the box it is a placeholder. A starter pack fills it: the two
SaaS starters, WorkOS and
Better Auth, ship the full admin below. The free
auth starter ships the minimal version: login, admin management,
and a dashboard that feature packs fill, without the users and organizations screens.
What's inside
The admin app has its own login and a set of private, role-gated screens:
| Screen | Route | Who can open it | Starter |
|---|---|---|---|
| Dashboard | / | Any admin | All |
| Users | /users, /users/$userId | Any admin | SaaS starters |
| Organizations | /orgs, /orgs/$organizationId | Any admin | SaaS starters |
| Settings | /settings | Super admin only | All |
| Login | /login | Public | All |
An organization's detail page has Overview and Members tabs, plus a Flags tab for its feature flags. Feature packs add tabs next to those.
On the backend, the starter adds /admin API routes (dashboard stats, users, orgs) and /admins/* for super-admin CRUD
of admin records. An admin Prisma model stores who has access beyond the bootstrap list.
How super-admin auth works
Admin access uses an AdminRole (super_admin or admin). Resolution order:
SUPER_ADMIN_EMAIL- comma-separated emails. Matching sign-ins are alwayssuper_admin, even with no row in theadmintable. This is how you get the first operator in.admintable - super admins add and manage these from the Settings screen. Middleware iswithAdmin/withSuperAdminon top of the same identity provider as the customer app.
Helpers live under apps/backend/src/features/admin/helpers/super-admin.ts and
apps/backend/src/utils/auth/admin-hono.ts (getAdminApp()).
Set the bootstrap list in apps/backend/.env:
SUPER_ADMIN_EMAIL=you@example.com,teammate@example.comHow feature packs extend the admin
Feature packs do not ship a second admin app. They plug into this one through pack manifests and codemods:
| Extension point | Manifest field | What packs do |
|---|---|---|
Nav slot admin | nav with slot: "admin" | Adds sidebar entries (for example Notifications, Newsletter, AI chat) |
| Dashboard rows | dashboardRows | Injects metric cards into the dashboard |
| Org detail rows / cards | orgDetailsRows, orgDetailsCards | Composes billing or usage cards onto an organization's page |
| Org detail tabs | orgDetailsTabs | Adds a whole tab next to Overview, Members, and Flags |
| Extra routes / features | routes, files | Adds API routes and admin UI modules under apps/admin and apps/backend |
Examples:
- Billing (any provider) adds a revenue row on the dashboard, org
billing and revenue cards, and
/admin/billingAPI routes. - Notifications adds a Notifications section with campaigns and an engagement widget.
- Newsletter adds the email studio, releases, and audience pages.
- AI Chat adds a usage dashboard and an AI usage tab on every organization.
Install any of these on top of a starter and the widgets and routes show up automatically after the CLI finishes.
Where to add your own admin screens
- Add a route under
apps/admin/src/routes/(private)/... - Add backend routes under
apps/backend/src/routes/admin-...mounted withgetAdminApp()(orwithSuperAdminwhen only super admins should call them) - Gate UI with the same admin role checks the starter already uses
- Prefer composing into the dashboard or org detail pages when the feature is a metric or a card, not a whole section
Running it
pnpm devNx starts frontend, backend, and admin with hot reload. Sign in with a SUPER_ADMIN_EMAIL account.
Environment variables
| Variable | Purpose |
|---|---|
SUPER_ADMIN_EMAIL | Comma-separated bootstrap super admin emails. Optional but you need at least one to sign in the first time. |