# Admin tasks on the server with the service_role key

> Create, invite, ban and delete users from your server or an Edge Function with the service_role key. How to set up an admin client safely and check that the caller is allowed.

Source: https://supahost.app/kb/server-side-admin-tasks · Updated 2026-11-11

User management (creating, inviting, banning and deleting users, or changing their `app_metadata`) goes through Auth's admin API. It needs the `service_role` key, which bypasses Row Level Security and can do anything in your project. Use it only in server code or Edge Functions, and check who is asking before you act.

## Get the key

Dashboard → your project → **Connect** → **App** → `service_role` (labelled "secret") → **Reveal**. Only the instance owner can reveal it. Store it as a server environment variable, never with a `NEXT_PUBLIC_` or `VITE_` prefix, and never in a mobile app.

Edge Functions don't need it copied in: SupaHost injects `SUPABASE_SERVICE_ROLE_KEY` into every function, along with `SUPABASE_URL` and `SUPABASE_ANON_KEY`.

## Create an admin client

```ts
import { createClient } from "@supabase/supabase-js";

export const admin = createClient(process.env.SUPABASE_URL!, process.env.SUPABASE_SERVICE_ROLE_KEY!, {
  auth: { autoRefreshToken: false, persistSession: false },
});
```

Keep this client for admin work only. If you call `signInWithPassword()` on it, it starts sending that user's token instead of the key, and RLS applies again.

## Common tasks

```ts
// Invite a teammate to a team chat app
await admin.auth.admin.inviteUserByEmail("sam@chat.example", {
  redirectTo: "https://chat.example.com/welcome",
});

// Create a confirmed user, for example during an import
await admin.auth.admin.createUser({
  email: "ops@chat.example",
  password: crypto.randomUUID(),
  email_confirm: true,
  app_metadata: { role: "moderator" },
});

// Change roles; app_metadata can't be edited by the user
await admin.auth.admin.updateUserById(userId, { app_metadata: { role: "admin" } });

// Ban for a week, then lift the ban
await admin.auth.admin.updateUserById(userId, { ban_duration: "168h" });
await admin.auth.admin.updateUserById(userId, { ban_duration: "none" });

// Delete a user
await admin.auth.admin.deleteUser(userId);

// Page through users
const { data } = await admin.auth.admin.listUsers({ page: 1, perPage: 100 });
```

The full list is in the [admin API reference](https://supabase.com/docs/reference/javascript/admin-api).

## Check the caller first

An admin endpoint must confirm that the person calling it is allowed to. This Edge Function lets only users with `app_metadata.role = "admin"` delete accounts:

```ts
import { createClient } from "npm:@supabase/supabase-js@2";

const url = Deno.env.get("SUPABASE_URL")!;
const admin = createClient(url, Deno.env.get("SUPABASE_SERVICE_ROLE_KEY")!, {
  auth: { autoRefreshToken: false, persistSession: false },
});

Deno.serve(async (req) => {
  const token = req.headers.get("Authorization")?.replace("Bearer ", "") ?? "";
  const { data: { user } } = await admin.auth.getUser(token);

  if (!user || user.app_metadata?.role !== "admin") {
    return Response.json({ error: "Forbidden" }, { status: 403 });
  }

  const { userId } = await req.json();
  const { error } = await admin.auth.admin.deleteUser(userId);
  if (error) return Response.json({ error: error.message }, { status: 400 });
  return Response.json({ deleted: userId });
});
```

Check `app_metadata`, not `user_metadata`. Users can change their own `user_metadata`.

## On SupaHost

- Deleting a user fails if rows in your tables reference them without `on delete cascade`. See [Errors creating, updating or deleting users](/kb/dashboard-user-errors).
- Invites are sent with your project's **Invite** template, from `noreply@supahost.app`.
- Anyone holding the `service_role` key has full access, and keys can't be rotated from the dashboard. Keep it out of logs, repos and client bundles. See [Rotating API keys and the JWT secret](/kb/rotating-jwt-secret).

## Related

- [Managing users (admin API)](/docs/auth/user-management)
- [Keeping keys safe](/docs/security/api-key-safety)
- [service_role client still gets RLS errors](/kb/service-role-rls-errors)
- [Roles, custom claims and RBAC](/docs/auth/custom-claims-rbac)
