The Supabase anon key (now also called the publishable key) is a public key designed to ship to the browser; it respects Row Level Security, so it can only do what your RLS policies allow. The service_role key (now also called the secret key) bypasses RLS entirely and has full admin access to your database, so it must only ever be used server-side and must never appear in client code, a public repo, or an environment variable with a NEXT_PUBLIC_ prefix.
The anon (publishable) key
Safe to expose, by design. It identifies your Supabase project and carries the authenticated user's JWT permissions when a user is signed in, or the anon role's permissions when no one is. Its safety depends entirely on Row Level Security being configured: with RLS on and sensible policies, the anon key can only do what you explicitly allow. With RLS off, it can read and write every row in every table. This is the key that belongs in the browser.
The service_role (secret) key
Never safe to expose. It bypasses all Row Level Security and grants full admin access to your database — the equivalent of connecting directly as a superuser. It belongs only in server code: Next.js route handlers, server actions, Edge Functions, or a separate backend process. It must be read from a server-only environment variable with no NEXT_PUBLIC_ prefix and must never appear in a client component, a Vite import.meta.env value used in the UI, or any code that gets bundled and sent to the browser.
Which key goes where — code example
The anon key is the only one that belongs in client code. In Next.js it carries the NEXT_PUBLIC_ prefix precisely because it is meant to be public. The service_role key must be read from a server-only environment variable and used only in code that never reaches the browser.
// Client component or browser code: anon key is safe here
import { createClient } from '@supabase/supabase-js'
const supabase = createClient(
process.env.NEXT_PUBLIC_SUPABASE_URL!,
process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!, // public, respects RLS
);
// Server route handler or Edge Function ONLY
// Never import this client in a 'use client' file
const adminClient = createClient(
process.env.SUPABASE_URL!,
process.env.SUPABASE_SERVICE_ROLE_KEY!, // NO NEXT_PUBLIC_, never in the browser
{ auth: { autoRefreshToken: false, persistSession: false } }
);How to find your keys in the Supabase dashboard
Go to your project in app.supabase.com, then open Project Settings in the left sidebar and click API. You will see two keys listed: the Project API key labeled anon / public (or publishable in newer projects) — this is safe to use in your frontend. Below it is the service_role key labeled secret — this one bypasses RLS and must stay on the server. Copy only the anon key into your NEXT_PUBLIC_SUPABASE_ANON_KEY variable. The service_role key goes into SUPABASE_SERVICE_ROLE_KEY with no public prefix.
Is the anon key safe to expose?
Yes, but only if Row Level Security is turned on for every table it can reach. The anon key is public on purpose, so its safety is not about hiding it — it is about your RLS policies. With RLS off, that public key can read and write your whole database, which is how most Supabase data-exposure incidents happen. AI builders using Lovable, Bolt, or v0 often ship the anon key to the browser without enabling RLS, leaving the database open to anyone who inspects the page source.
Anon key vs publishable key: same key, new name
Supabase renamed its API keys in 2024: the anon key is now called the publishable key, and the service_role key is now called the secret key. The behavior is identical — nothing changed except the label. If a newer project shows a publishable key in the dashboard, that is your anon key and it is safe in the browser as long as RLS is on. Everything written about the anon key applies to the publishable key and vice versa.
When is it legitimate to use the service_role key?
Use it only when you deliberately need to bypass RLS for a trusted server-side operation: seeding or migrating data, admin tasks that act across all users, background jobs that need unrestricted read access, or webhook handlers that must write without a user session. In all cases it must run inside a server function or Edge Function, read the key from an environment variable, and never be accessible to the browser. If you find yourself reaching for it because a query fails with the anon key, that is usually a sign that your RLS policies need work, not that you should escalate to the service_role key.
What happens if the service_role key leaks
Full database takeover. Because it bypasses RLS, anyone holding it can read every row, write or delete any record, and drain or wipe your data with no login required. If the key has ever appeared in a browser bundle, a public GitHub repo, a client-side environment variable, a screenshot, or a commit — treat it as compromised and rotate it immediately in the Supabase dashboard under Project Settings, API. After rotating, move the new key into a server-only variable and audit your database logs for unexpected access while the old key was live.
Anon key vs service_role key at a glance
| anon / publishable key | service_role / secret key | |
|---|---|---|
| Also called | publishable key (new name) | secret key (new name) |
| Respects RLS | Yes | No — bypasses it entirely |
| Safe in the browser | Yes, when RLS is configured | Never |
| Where it belongs | Client components and server code | Server-only: route handlers, Edge Functions, backends |
| Next.js env prefix | NEXT_PUBLIC_SUPABASE_ANON_KEY | SUPABASE_SERVICE_ROLE_KEY (no public prefix) |
| If accidentally exposed | Usually fine when RLS is on | Full database takeover — rotate immediately |
| Typical use case | All normal frontend queries | Admin tasks, migrations, trusted background jobs |
What this means for AI-generated code
AI coding tools like Lovable, Bolt, and Cursor often reach for the service_role key to make a query work without configuring RLS, and then place it in client-reachable code or a NEXT_PUBLIC_ variable. If that key ends up in a browser bundle, anyone who views your page source has full admin access to your database. We found this pattern in roughly 1 in 20 AI-built apps we scanned. Rotate the key immediately if it was ever exposed, then wire up RLS policies so the anon key is sufficient for normal queries.
Common questions
Is the Supabase anon key safe to expose?
Yes. The anon (publishable) key is designed to be public and ship to the browser. It is only safe if Row Level Security is enabled on every table it can reach — without RLS policies the key can read and write everything. Turn RLS on before treating the anon key as safe.
Where do I use the service_role key?
Only on the server: Next.js route handlers, server actions, Supabase Edge Functions, or a separate backend. Read it from a server-only environment variable with no NEXT_PUBLIC_ prefix. Never reference it in a client component or commit it to a public repository.
What is the difference between the anon key and the publishable key?
They are the same key. Supabase renamed the anon key to the publishable key (and the service_role key to the secret key) in 2024. The behavior is identical, so everything written about the anon key applies to the publishable key.
My service_role key was exposed — what do I do?
Rotate it immediately in the Supabase dashboard under Project Settings, API. Move the new key into a server-only environment variable with no public prefix. Then check your database logs for any unexpected reads or writes that occurred while the old key was live.
Can I use the anon key on the server?
Yes. The anon key works on both client and server. Use the service_role key server-side only when you deliberately need to bypass RLS — for example, an admin migration or a background job that must act across all users.
Why does my query fail with the anon key but work with service_role?
Your RLS policy is blocking the operation, which is RLS doing its job. Do not switch to the service_role key to fix this — instead, add or fix the RLS policy so the anon key is allowed to do what you need. Using service_role to bypass a policy failure is how service_role keys end up in client code.
What is the anon key used for exactly?
It identifies your Supabase project and passes the authenticated user's JWT (or the anon role if no user is signed in) with every database request. Supabase reads that JWT to decide which RLS policies to apply. All normal frontend database queries — fetching records, inserting rows for the current user — should go through the anon key.
Does the anon key expire?
No. The anon key is a static project key that does not expire. The user's access token (JWT) inside a session does expire, but the anon key itself is permanent. You can rotate it manually in the Supabase dashboard if you need to invalidate it.