Skip to content

Admin panel

The admin panel is for you, the operator. It lists everyone who signed up and what they pay, and has two support tools.

  1. Put your address in .env, several separated by commas:

    Terminal window
    ADMIN_EMAILS=you@example.com
  2. Restart the API, and sign up or sign in with that address. It must be verified, which is what stops a stranger from signing up with it.

  3. Open the menu at the foot of the sidebar and choose Admin.

ADMIN_EMAILS is the only way in, and it is checked on every request: take an address off and that person loses access on their next one. In production, treat those inboxes as keys to the product.

An overview (sign-ups in the last 7 days, subscriptions per plan, failed payments), everyone who signed up with search, and, with teams on, every workspace with its plan and subscription.

Sign in as next to a person gives you their session, to see what they see. A banner stays up with a Stop button. It lasts an hour at most, and they notice nothing.

While you are them, you are not an admin: the panel is gone and @AdminOnly() routes answer 403. What you do is recorded in their activity log, marked “by support”.

Suspending signs them out everywhere and refuses every sign-in after. Their data stays. It does not cancel their subscription: if they pay, end it in Stripe or Polar yourself. Lift suspension lets them back in.

Mark a controller or route with @AdminOnly(); everyone else gets 403. Put code that reads across every workspace in AdminService, and admin screens in apps/web/src/routes/_authenticated/admin.

Admin is not a workspace role. @RequireRole('owner') is about one workspace; @AdminOnly() is about the whole product. Being an admin gives no access to anyone’s workspace.