What is Service-role key?

Updated

Supabase's privileged API key that bypasses every Row-Level-Security policy. Designed for server-side use only. If it ships in a client bundle, your database is fully open regardless of the policies you wrote.

Full explanation

Supabase issues two API keys per project: the `anon` key (safe for client use, gated by RLS) and the `service_role` key (god-mode, bypasses RLS, has Postgres-superuser privilege). The service-role key exists for legitimate server-side use cases — webhook handlers verifying their own signatures, scheduled jobs running on your backend, admin operations after auth. The bug pattern: AI-generated code or starter templates that put the service-role key in `lib/supabase.ts` imported by client components, shipping the key in the JavaScript bundle every visitor downloads.

Example

lib/supabase.ts uses createClient(SUPABASE_URL, SUPABASE_SERVICE_ROLE_KEY) and is imported by app/page.tsx. The service-role key is bundled into the client JavaScript. Any visitor's browser can extract the key from the bundle source and read or write any row in any table, regardless of RLS policies. The fix: use anon key client-side, wrap service-role usage behind `import "server-only"` so the bundler errors on accidental client import.

Related

FAQ

How do I tell if a Supabase JWT is anon or service-role?

Decode the JWT (jwt.io or `echo $JWT | cut -d. -f2 | base64 -d | jq .role`). The `role` claim says either `anon` or `service_role`. Service-role JWTs in any code path reachable from the browser must be rotated immediately.

When is service-role usage safe?

When the file is wrapped in `import "server-only"` (Next.js compile-time enforcement) and the env var name does not start with `NEXT_PUBLIC_`. The legitimate use cases are webhooks, scheduled jobs, and admin operations after server-side auth.