Admin dashboard
Revenue and sign-up analytics, recent payments and user management at /admin.
Make yourself an admin
Sign up in the app first, then run:
npm run make-admin -- you@example.comyou@example.com is now an admin. Open /admin.This reads DATABASE_URL from .env.local. Production uses the same database when you deployed with setup, so the same command works for your live site. If production has its own database, run it with that URL:
DATABASE_URL="postgresql://…production…" node --env-file-if-exists=.env.local scripts/make-admin.mts you@example.comWhat's on it
Pick a period: 7 days, 30 days, 90 days or 12 months. Each number is compared with the period before it.
| Metric | What it counts |
|---|---|
| Net revenue | What reached your Dodo balance from payments in the period (the settlement amount, after Dodo's fees and taxes), in your payout currency. Refunded payments don't count |
| MRR | Monthly recurring revenue right now from active subscriptions (yearly plans divided by 12), with the number of subscribers |
| New customers | Users whose first payment was in the period |
| Sign-ups | New accounts |
| Conversion | Share of the period's sign-ups who have paid |
| Revenue by plan | Which plans earn the most |
| Recent payments | The latest charges, with user and amount |
Below that is a searchable user list with each user's plan, and Ban / Unban buttons. A banned user is signed out and can't sign in again.
Who can see it
Only users with the admin role. Everyone else, signed in or not, gets a 404 Not Found, not a "forbidden" page, so the admin area isn't advertised. Ban and unban go through Better Auth's admin API, which checks the role again.
To remove someone's admin role, run in the Neon SQL editor:
update "user" set role = 'user' where email = 'someone@example.com';Adding your own admin pages
Put them under src/app/admin/, and call requireAdmin() from src/lib/admin.ts inside a <Suspense> boundary, like requireUser() in Authentication. The admin area always uses the light theme.