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.

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

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

// Invite a teammate to a team chat app
await admin.auth.admin.inviteUserByEmail("[email protected]", {
  redirectTo: "https://chat.example.com/welcome",
});

// Create a confirmed user, for example during an import
await admin.auth.admin.createUser({
  email: "[email protected]",
  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.

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:

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

Updated 11 November 2026 · View as Markdown · Something wrong or missing? Tell us