Skip to main content
The admin router provides endpoints for system administration. All endpoints require the admin role.

Get system stats

Retrieve system-wide statistics.
const stats = await client.admin.getSystemStats();

Response

totalUsers
number
Total number of users
totalBooks
number
Total number of books in the system
totalLibraries
number
Total number of libraries
totalOrganizations
number
Total number of organizations

List users

Get all users in the system.
const users = await client.admin.listUsers();

Response

Array of user objects:
id
string
User ID
email
string
User email
name
string | null
User display name
role
'user' | 'admin'
User role
banned
boolean
Whether user is banned
emailVerified
boolean
Whether email is verified
createdAt
string
ISO timestamp

Ban user

Ban a user from the system.
await client.admin.banUser({
  userId: "user-id",
});

Input

userId
string
required
User ID to ban

Response

success
boolean
Always true

Unban user

Remove a ban from a user.
await client.admin.unbanUser({
  userId: "user-id",
});

Input

userId
string
required
User ID to unban

Response

success
boolean
Always true

Set user role

Change a user’s role.
await client.admin.setUserRole({
  userId: "user-id",
  role: "admin",
});

Input

userId
string
required
User ID
role
enum
required
New role: user or admin

Response

success
boolean
Always true

List organizations

Get all organizations in the system.
const orgs = await client.admin.listOrganizations();

Response

Array of organization objects:
id
string
Organization ID
name
string
Organization name
slug
string
Organization slug (unique identifier)
createdAt
string
ISO timestamp
memberCount
number
Number of members

Create organization

Create a new organization.
const org = await client.admin.createOrganization({
  name: "Acme Corp",
  slug: "acme-corp",
});

Input

name
string
required
Organization name (minimum 1 character)
slug
string
required
Organization slug (minimum 1 character, must be unique)

Response

Created organization object.

Delete organization

Delete an organization and all its data.
await client.admin.deleteOrganization({
  orgId: "org-id",
});

Input

orgId
string
required
Organization ID

Response

success
boolean
Always true

Get organization with members

Retrieve detailed organization information including all members.
const org = await client.admin.getOrgWithMembers({
  orgId: "org-id",
});

Input

orgId
string
required
Organization ID

Response

id
string
Organization ID
name
string
Organization name
slug
string
Organization slug
members
array
Array of member objects
id
string
Member ID
userId
string
User ID
organizationId
string
Organization ID
role
string
Member role in organization
user
object
User information

Remove member

Remove a member from an organization.
await client.admin.removeMember({
  memberId: "member-id",
});

Input

memberId
string
required
Member ID (not user ID)

Response

success
boolean
Always true

Update member role

Change a member’s role within an organization.
await client.admin.updateMemberRole({
  memberId: "member-id",
  role: "admin",
});

Input

memberId
string
required
Member ID
role
string
required
New role (organization-specific role)

Response

success
boolean
Always true

Backfill cover colors

Trigger a background job to extract dominant colors from book covers that are missing color information.
const result = await client.admin.backfillCoverColors();
console.log(`Enqueued ${result.enqueued} books`);

Response

enqueued
number
Number of books queued for color extraction

Admin authorization

All admin endpoints use adminProcedure, which requires:
  1. An authenticated session (UNAUTHORIZED if missing)
  2. The admin role (FORBIDDEN if not admin)
const requireAdmin = o.middleware(async ({ context, next }) => {
  if (!context.session?.user) {
    throw new ORPCError("UNAUTHORIZED");
  }
  if (context.session.user.role !== "admin") {
    throw new ORPCError("FORBIDDEN");
  }
  return next({
    context: {
      session: context.session,
    },
  });
});

export const adminProcedure = publicProcedure.use(requireAdmin);

Example: Admin dashboard

import { orpc } from "@/utils/orpc";

function AdminDashboard() {
  const { data: stats } = orpc.admin.getSystemStats.useQuery();
  const { data: users } = orpc.admin.listUsers.useQuery();
  const { data: orgs } = orpc.admin.listOrganizations.useQuery();
  
  const banUser = orpc.admin.banUser.useMutation();
  const setRole = orpc.admin.setUserRole.useMutation();
  
  return (
    <div>
      <h1>System stats</h1>
      <div>
        <p>Users: {stats?.totalUsers}</p>
        <p>Books: {stats?.totalBooks}</p>
        <p>Libraries: {stats?.totalLibraries}</p>
        <p>Organizations: {stats?.totalOrganizations}</p>
      </div>
      
      <h2>Users</h2>
      <table>
        <thead>
          <tr>
            <th>Email</th>
            <th>Role</th>
            <th>Banned</th>
            <th>Actions</th>
          </tr>
        </thead>
        <tbody>
          {users?.map((user) => (
            <tr key={user.id}>
              <td>{user.email}</td>
              <td>{user.role}</td>
              <td>{user.banned ? "Yes" : "No"}</td>
              <td>
                <button onClick={() => banUser.mutate({ userId: user.id })}>
                  {user.banned ? "Unban" : "Ban"}
                </button>
                <button onClick={() => setRole.mutate({
                  userId: user.id,
                  role: user.role === "admin" ? "user" : "admin",
                })}>
                  Toggle admin
                </button>
              </td>
            </tr>
          ))}
        </tbody>
      </table>
    </div>
  );
}

Build docs developers (and LLMs) love