Source: https://www.hype-stack.dev/stacks/react-admin-panel-template

# React Admin Panel Template as Source Code, Not a Generator

> 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

|  | Hype Stack | Auto-CRUD generator | Hand-built |
| --- | --- | --- | --- |
| First screen | Minutes: point it at a schema and it renders | Minutes: generate the app and rebrand it | Days, per screen |
| Custom logic | Whatever you want, you wrote it | Ordinary React and Hono code in your repo | Escape hatches, overrides, then a rewrite |
| Auth boundary | Shared session, then a role looked up in a staff table the customer has no row in | Usually a flag on the customer's own user record | Depends on the authentication and authorization design |
| Deploy target | Your choice | Its own Vite build, so it can sit behind a VPN or allowlist | A route inside your public app |
| In five years | Exactly what you maintained | Source you own, no upstream to track | A dependency whose opinions you inherited |

[Browse all packs](/packs)

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()` is `new Hono().use(withAuthUser).use(withAdmin)`, so a
  request carries the same session cookie a customer would, and then `withAdmin` looks 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_admin` and `admin`, and they live in an
  `Admin` table that has nothing to do with the organization roles your tenants use. Being `manage:all` inside a tenant
  does not grant access here. Managing other admins is restricted to `super_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:

```ts
// 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.

**your-app** — arrives with the starter pack

- apps/
  - frontend/ — the public app, on your CDN
  - admin/ (added) — its own Vite build, so it can sit behind a VPN
  - backend/
    - src/middleware/ (added) — admin requests use admin middleware
    - src/features/admin/ (added) — platform roles, not tenant roles
    - src/routes/admin/ (added) — the access matrix test lives here

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**

Inside the customer app to A separately deployed private app:

- **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.



The user and organization model the admin reads is in
[Better Auth SaaS Template with Organizations Built In](/stacks/better-auth-saas-template), and a CRM built on the same
shell in [CRM Template](/stacks/crm-template).
