Inksh logoContactAbout usPrivacy
Author name Hemant Bhatt

October 16, 2025

Unleashing Server-Side Superpowers: Mastering Supabase's elevated previlege key

Supabase Service Role Key - Master key unlocking server-side superpowers

Introduction

Your Supabase project needs two types of API keys: the public-facing keys (anon or publishable) that play by Row Level Security rules, and the admin keys (service_role or secret) which are like VIP access pass that bypasses every velvet rope. One's for your users; the other's for your server when it needs to get things done without asking permission.

API Keys Evolution: Legacy vs. New Format

Supabase has introduced a new, more secure API key format while maintaining backward compatibility:

Legacy Format

  • anon - Public, client-safe
  • service_role - Admin privileges

New Format

  • publishable_key - Public, client-safe
  • secret_key - Admin privileges

Both formats work identically and provide the same level of access. The new secret_key key offer enhanced security with the ability to create multiple secret keys and revoke them individually—perfect for managing different services or rotating keys.

Why You Need Admin API Keys

Admin API keys (either the legacy service_role or new secret keys) grant administrative privileges across your Supabase project:

When to Use It

Use admin keys (service role or secret) exclusively in server-side contexts:

  • Server Actions
  • API Routes (app/api/...)
  • Server Components
  • Backend scripts and migrations
  • Scheduled jobs and webhooks

Setting Up Your Admin API Key

Step 1: Choose Your API Key Format

In your Supabase project dashboard, navigate to SettingsAPI Keys. You'll see two tabs:

  1. Legacy API Keys :
    • anon / public - Safe for client-side use
    • service_role - Your admin key
  2. API Keys (New format with enhanced security):
    • Publishable key - Safe for client-side use
    • Secret keys - Admin privileges, can create multiple and revoke individually

Which should you use?

Both formats work identically and provide the same admin privileges. However, the new secret keys offer advantages:

  • Create multiple secret keys for different services
  • Revoke individual keys without affecting others
  • Better key rotation and management
  • Future-proof as Supabase evolves

Recommendation: Use the new secret keys for new projects. Existing projects can continue using service_role keys or migrate gradually.

Step 2: Store It Securely in Environment Variables

Create or update .env.local in your Next.js project root. You can use either the legacy or new key format:

.env.local
# Public keys - safe for client
NEXT_PUBLIC_SUPABASE_URL=https://your-project.supabase.co
NEXT_PUBLIC_SUPABASE_PUBLISHABLE_KEY=your-anon-key-here

# Option 1: Legacy service_role key
SUPABASE_SERVICE_ROLE_KEY=your-service-role-key-here

# Option 2: New secret key (recommended for new projects)
# SUPABASE_SECRET_KEY=sb_secret_...

Critical Security Warning:

Variables prefixed with NEXT_PUBLIC_ are exposed to the browser. Your admin keys (whether service_role or secret) must NEVER EVER have this prefix. Both formats bypass Row Level Security and provide full database access.

Step 3: Create a Server-Side Supabase Client

Create a utility file for your admin client. This example shows both key formats:

lib/supabase-admin.ts
1import "server-only";
2import { createClient } from "@supabase/supabase-js";
3
4const supabaseUrl = process.env.NEXT_PUBLIC_SUPABASE_URL!;
5
6// Use either the legacy service_role key or the new secret key
7// Both provide identical admin privileges
8const supabaseAdminKey =
9 process.env.SUPABASE_SECRET_KEY || // New format (recommended)
10 process.env.SUPABASE_SERVICE_ROLE_KEY!; // Legacy format (still works)
11
12export const supabaseAdmin = createClient(supabaseUrl, supabaseAdminKey, {
13 auth: {
14 autoRefreshToken: false,
15 persistSession: false,
16 },
17});

This creates a dedicated admin client for server-side code only. The server-only import ensures this file cannot accidentally be imported in client components.

Practical Examples

Example 1: Server Action - Deleting Users Programmatically

Remove users from your system through an admin panel using a Server Action:

app/actions/delete-user.ts
1"use server";
2
3import { supabaseAdmin } from "@/lib/supabase-admin";
4import { getCurrentUser } from "@/lib/auth";
5
6export async function deleteUserAsAdmin(userId: string) {
7 // Check if the current user is an admin
8 const currentUser = await getCurrentUser();
9
10 if (!currentUser?.is_admin) {
11 return { success: false, error: "Unauthorized: Admin access required" };
12 }
13
14 // Perform the admin operation
15 const { data, error } = await supabaseAdmin.auth.admin.deleteUser(userId);
16
17 if (error) {
18 return { success: false, error: error.message };
19 }
20
21 return { success: true, message: "User deleted successfully" };
22}

Key insight:

Always verify authorization before executing admin operations. The .auth.admin API bypasses RLS whether you use the legacyservice_role key or new secret keys, but you must implement your own permission checks.

Example 2: Storage Management - List All Files in a Bucket

List all files and folders within a bucket path, bypassing storage policies:

app/actions/list-bucket-files.ts
1"use server";
2
3import { supabaseAdmin } from "@/lib/supabase-admin";
4import { getCurrentUser } from "@/lib/auth";
5
6export async function listBucketFiles(bucketName: string, folderPath: string) {
7 // Check if the current user is an admin
8 const currentUser = await getCurrentUser();
9
10 if (!currentUser?.is_admin) {
11 return { success: false, error: "Unauthorized: Admin access required" };
12 }
13
14 // List all files in the bucket, ignoring storage policies
15 const { data, error } = await supabaseAdmin.storage
16 .from(bucketName)
17 .list(folderPath, {
18 limit: 100,
19 offset: 0,
20 sortBy: { column: 'name', order: 'asc' },
21 });
22
23 if (error) {
24 return { success: false, error: error.message };
25 }
26
27 return { success: true, files: data };
28}

For the full list of available Supabase admin operations, check out the official Supabase JavaScript documentation.

Common Pitfalls and How to Avoid Them

Pitfall #1: Using Admin Keys in Client Components

The Problem: Accidentally importing the admin client in a file with 'use client' at the top exposes your admin key to the browser.

The Fix: Double-check your imports. Mark server modules or utilities with import 'server-only' to prevent accidental client-side usage. Read more about nextjs server patterns.

Pitfall #2: Skipping Authorization Checks

The Problem: Assuming that because your Server Action uses an admin key, it's automatically secure. Server action can be called by anyone.

The Fix: Always implement authorization checks before running admin logic. Verify the user's identity and permissions before executing admin operations.

Conclusion

Supabase admin API keys unlock server-side administrative capabilities in Next.js. Whether you use the legacy service_role JWT or the new secret key format, they enable operations that transcend user-level permissions, from managing authentication to bypassing RLS for bulk operations.

Key takeaways:

  • Choose your format: Legacy service_role keys still work perfectly, but new secret keys offer better management.
  • Use only in server-side contexts (Server Actions, API Routes, Server Components).
  • Never expose to the client—both formats bypass RLS and provide full access

You're now equipped to build secure, powerful admin features. Remember: with great admin keys comes great responsibility (and hopefully, good backups). 🚀