Stack
React Admin Panel Template
Every admin panel starts as one protected page for support. Six months later it can write to production, and nobody decided that.
Admin panels often start as small internal tools. Support needs to look up a user, so someone adds a protected page. Over time, that page may gain subscription controls and other writes to production without a corresponding permission model or deployment boundary.
Picking a react admin panel template includes deciding where that boundary sits and how much of it the template enforces in code.
Three ways to get an admin, and what each costs
An auto-CRUD generator is usually faster for the first screen. If your admin covers five tables and serves two trusted people, that may be enough. This template is aimed at admin tools that need custom business logic, broader staff access, or a separate deployment boundary.
The authorization and deployment boundary
apps/admin is its own application in the monorepo with its own Vite build, next to the public frontend and the Hono
backend. That separation has three practical effects:
- Its own authorization, on a shared session.
getAdminApp()isnew Hono().use(withAuthUser).use(withAdmin), so a request carries the same session cookie a customer would, and thenwithAdminlooks the user up and throws a 403 unless they are staff. The session is shared. The authorization is not, and it is a lookup rather than a flag. - Its own role vocabulary, in its own table. Platform roles are
super_adminandadmin, and they live in anAdmintable that has nothing to do with the organization roles your tenants use. Beingmanage:allinside a tenant does not grant access here. Managing other admins is restricted tosuper_admin. - Its own deploy. Because it builds separately, you can put it behind a VPN, an IP allowlist, or a private hostname without touching the public frontend or its CDN config.
The first admin needs a bootstrap path. This template resolves it through an environment variable:
// features/admin/helpers/super-admin.ts
export const resolveAdminAccess = async ({
userId,
email,
}: {
userId: string;
email?: string | null;
}): Promise<AdminRole | null> => {
if (isSuperAdminEmail(email)) return AdminRole.SUPER_ADMIN;
const row = await getAdminQuery({ email, userId });
return row?.role ?? null;
};SUPER_ADMIN_EMAIL is a comma-separated environment variable, and the addresses in it are super admins whether or not
they have a row in the admin table. Nothing is auto-inserted. A fresh database has an owner because the environment
says so, and revoking that access requires changing the environment and deploying again.
An integration test verifies this behavior. admin.access-matrix.integration.test.ts runs five cases against real
routes: a signed-in non-admin gets a 403, an admin is admitted and reports ADMIN, a bootstrap super admin is admitted
and reports SUPER_ADMIN with no database row, a plain admin is blocked from the super_admin-only surface, and a
super admin can list it. If a change loosens the middleware, that file fails.
- apps/
- frontend/the public app, on your CDN
the public app, on your CDN
- admin/its own Vite build, so it can sit behind a VPN
its own Vite build, so it can sit behind a VPN
- backend/
- src/middleware/admin requests use admin middleware
admin requests use admin middleware
- src/features/admin/platform roles, not tenant roles
platform roles, not tenant roles
- src/routes/admin/the access matrix test lives here
the access matrix test lives here
Two builds, one backend, one session. A customer's cookie reaches the admin routes and gets a 403 there, from a role lookup against a table they have no row in, rather than from a flag on their own user record.
It shares the typed SDK with everything else, so an endpoint added for an admin screen is typed on the client the same way a customer endpoint is.
What the included screens support
One useful way to judge an admin panel is to map its screens to the support and operations questions it can answer:
| Somebody asks | Where it gets answered |
|---|---|
| "I can't get into my account" | Users: search, open a user, edit them, remove a membership |
| "Can you move me to the other company's workspace?" | Organizations: open an org, edit it, remove a member |
| "Are we growing?" | Dashboard: new users over thirty days, org count, a signups chart, five most recent signups |
| "Give the new support hire access" | Admins: invite and manage platform admins, super_admin only |
Those are queries against your own data, written as ordinary feature modules. Adding a widget means adding a Hono route and a card rather than using a plugin extension API. That gives you room to support questions the included screens do not cover.
Where the admin comes from
The template reserves apps/admin, but the screens, the admin middleware, and the platform role model arrive with a
paid SaaS starter pack. The free open-source core ships the frontend and backend without them. So this page describes
what you get once the starter is composed in, not what a bare clone contains.
What it deliberately does not have
There is no audit log UI. If you need "who changed this record and when" for compliance, you must add it. Audit scope and retention requirements vary by company, so the template does not prescribe a generic implementation.
This gap matters because an admin panel contains write endpoints pointed at production. The authorization boundary decides who can reach them. An audit log records what they changed.
How far to take the boundary
Admin panels can sit at different points on the same isolation axis. Small internal tools often begin inside the main application, while tools with stricter access requirements may justify a separate build, session, or network.
The same admin panel, at four points in its life
A page behind a shared password
Same origin and bundle, with one shared credential protecting the page.
An /admin route with a role flag
One boolean on the user row separates a customer session from an admin write endpoint.
A separate build, and staff are their own table
Where this template actually sits. Its own Vite build, a role resolved against an Admin table rather than a column on the customer, and an access-matrix test that fails if that stops being true. The cookie is still the customer's cookie.
A separate session, on a network you have to be on
Its own identity provider or a second cookie, behind a VPN or an IP allowlist. Not in the box, and for most products not worth it. The separate build is what leaves the option open.
The separate build and staff table provide more isolation than an in-app route, while the shared session means this is not the strictest option shown here.
When evaluating an admin template, check how a request proves it is allowed and which tests fail if that protection is removed. Screens can be changed as requirements grow, but the access boundary affects every admin route.
The admin app is below, running, alongside the files it installs.
See it running
Templates are curated project starters built on this stack: a layout, feature packs, a custom theme, and bonus pages. The previews below are recordings of the real apps.
Two ways to install features
Same features underneath, different starting point. Either command resolves what the packs depend on, copies the source into your repository, and merges the Prisma schema.
Take a template
A landing page, a design system, a themed layout, and the features already wired into it. Rebrand it, put your product in the middle, ship.
npx @hype-stack/cli template$ hype-stack compose
Compose your own design
Your design and your choices, without rebuilding auth, billing, or notifications. Tick the packs you want and the CLI wires them into the open-source starter.
npx @hype-stack/cli composeInside the stack
What each pack gives you
Source code, not a dependency. Every pack lands in your repository across the surfaces the feature touches.
Authentication, organizations, roles, sessions, and a full admin app, powered by WorkOS.
- Email & social login
- Organizations & members
- Roles & permissions
- Admin app & dashboard
Questions, answered
More stacks
Turn your ideas into
Real applications.
Start free and own every line you ship. When you want more, one All-Access license unlocks every premium pack and template for a year.
