<AdminDashboard />

<AdminDashboard /> is the body of the admin dashboard: a Users tab and, with organizations on, an Organizations tab, where user and organization pages open in place of the table. The admin/routes item mounts it at /admin behind an admin check. Mount it yourself to put the dashboard inside an admin area of your own.
Quick start
With admin: true and the admin/routes item, /admin already works. To mount the dashboard on another page:
Then point ADMIN_ROUTE at that page. <AdminButton /> links to it, and Stop impersonating returns there:
The component needs the root layout from base: AuthProvider in SvelteKitConvexClientProvider in Next.js.
What it does
- Users tab: searches users by name or email and lists them newest first. Each row shows the avatar, name, email and join date, with You, Admin and Banned badges. Clicking a row opens the user’s page; the row menu holds the user actions.
- New user: opens the create-user dialog with name, email and password. With organizations on, it also picks an optional organization and a role in it, Member or Admin.
- User page: the user’s name, email and user id with a copy button, a ban notice with Lift ban, the editable profile, the user’s organizations and their active sessions.
- Organizations tab: searches organizations by name or slug. Clicking a row opens the organization’s page.
- Organization page: the logo, name and slug, the owner, and the members with their roles.
The tables and pages are Convex queries, so they update while you look at them: a user another admin bans shows the Banned badge without a reload.
User actions
The user page has the same menu as the row, without View profile. Which actions appear depends on the account:
| Account | Menu |
|---|---|
| A user | Copy user ID, View profile, Impersonate (not while banned), Make admin, Set password, Sign out everywhere, Ban or Unban, Delete user |
| Another admin | Copy user ID, View profile, Remove admin |
Yourself, or an ADMIN_USER_IDS admin | Copy user ID, View profile |
Ban asks for an optional reason and a duration: Permanent, 1 hour, 1 day, 7 days or 30 days. Make admin, Set password and Delete user open a dialog first. The roles section explains why the menu shrinks for admins; the server enforces the same rules.

On the user page:
- Profile edits the name and email. A new email counts as verified. The form is read-only for another admin until their admin role is removed.
- Organizations lists the user’s memberships with a role select, adds them to another organization and removes them from one. An owner’s role and membership stay; hand the ownership over on the organization’s page.
- Active sessions lists sessions newest first with the browser, IP address and sign-in time. Five show at first, then Show all. Impersonation sessions carry an Impersonation badge, and each session has its own Sign out.
Organizations

The row menu holds Copy organization ID, View organization and Delete organization.

On the organization page, an admin uploads or removes the logo, edits the name and slug, adds members, changes a member’s role between Admin and Member and removes members. Make owner and Transfer ownership hand the organization to another member, and the previous owner stays as an admin or member or leaves. Clicking a member opens their user page above the organization, with a back button named after it.
Page sizes and limits
| List | Without a search | With a search |
|---|---|---|
| Users | 50 per Load more, up to the newest 1000; past that, search | The first 50 matches, best first |
| Organizations | 25 per Load more, up to the newest 1000; past that, search | The first 25 matches |
| A user’s sessions | Five, then Show all | |
| An organization’s members | The first 200, the owner first |
A search runs 250 ms after the last keystroke and matches names, emails and slugs that contain every word you type, each at the start of a word. When the list fills up, refine the search.
Props
<AdminDashboard /> takes no props in either framework. Its state lives in the URL.
URL parameters
| Parameter | Value |
|---|---|
adminTab | users (the default) or organizations |
adminUser | The id of the user whose page is open |
adminOrganization | The id of the organization whose page is open; only read in the Organizations tab |
/admin?adminTab=organizations&adminOrganization=<organizationId> opens an organization’s page directly, and a reload keeps the view. Opening a page adds a history entry, so the browser’s back button returns to where you came from. Closing a page or switching tabs replaces the entry instead. The dashboard keeps the page’s other query parameters.
Behavior
The component doesn’t check that the visitor is an admin. Its queries do: for anyone else they throw Forbidden: application admin role required, and the dashboard shows the error where the table would be. Gate the page as the quick start does, with useIsAppAdmin(). It returns false while the check loads and for signed-out visitors, so that page renders nothing until it knows. In SvelteKit it returns an object, so read admin.isAppAdmin instead of destructuring it.In Next.js it returns the boolean.
The shipped /admin route handles each case on its own:
- While the session and the admin check load, it shows a spinner.
- A signed-out visitor goes to
/signin?redirectTo=/adminand comes back after signing in. - A signed-in user who isn’t an admin sees an Access denied page with Back to home. A comment in the route file shows how to answer 404 instead, if users shouldn’t learn the admin area exists.
Impersonate reloads the app at / as the impersonated user, and <ImpersonationBanner /> takes over from there.
Related
- Admin dashboard: setup, roles and the security model.
<AdminButton />: a link to the dashboard for admins.<ImpersonationBanner />: the banner shown while impersonating.