What is wired · 2 min
Admin
A read-only view of what is happening, gated on a list of emails rather than a column anyone could write.
/admin shows orders, users and revenue. It exists so you can answer "did that
payment go through" without opening the Stripe dashboard, and it deliberately
stops there.
Who gets in
ADMIN_EMAILS=you@example.com,cofounder@example.comAn environment variable, not an is_admin column. The reasoning is short: a
column on a table a user can update is a column a user can set. Even where the
policy currently forbids it, that is one careless update policy away from
being a privilege escalation, and it would look like a normal line of code in
review.
Changing who is an admin means a deploy. That is the right amount of friction for the list of people who can see everyone's data.
const user = await requireUser();
const admins = (process.env.ADMIN_EMAILS ?? "").split(",").map((s) => s.trim());
if (!admins.includes(user.email)) notFound();notFound() rather than a redirect, so the page does not confirm it exists to
somebody probing.
Why it is read-only
An admin panel that can edit rows needs an audit trail, a confirmation step and a way to undo, or it is a foot-gun with a nice font. Refunds belong in Stripe, where they already trigger your webhook. Data fixes belong in the Supabase SQL editor, where they are logged.
If you do add a write action, put it behind a server action that re-checks the admin list. Do not trust the page having rendered.
Extending it
The page is a single Server Component doing a handful of queries with
supabaseAdmin(). Add a query, add a table, keep it boring. It is the one
screen where a chart is worth less than a list of the last twenty rows.
Something wrong or missing on this page? Tell us.