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
- Deleting a user fails if rows in your tables reference them without
on delete cascade. See Errors creating, updating or deleting users. - Invites are sent with your project's Invite template, from
[email protected]. - Anyone holding the
service_rolekey 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.