[{"data":1,"prerenderedAt":825},["ShallowReactive",2],{"blog-how-to/supabase-new-api-keys":3},{"id":4,"title":5,"body":6,"category":794,"date":795,"dateModified":795,"description":796,"draft":797,"extension":798,"faq":799,"featured":797,"headerVariant":810,"image":811,"keywords":812,"meta":813,"navigation":373,"ogDescription":814,"ogTitle":811,"path":815,"readTime":816,"schemaOrg":817,"schemaType":818,"seo":819,"sitemap":820,"stem":821,"tags":822,"twitterCard":823,"__hash__":824},"blog/blog/how-to/supabase-new-api-keys.md","Supabase Is Retiring service_role Keys. Yours May Be in Your Frontend.",{"type":7,"value":8,"toc":787},"minimark",[9,18,43,66,73,78,81,146,149,152,177,181,184,199,209,222,226,237,310,403,510,521,525,542,598,643,654,673,689,693,696,704,757,775,783],[10,11,12,13,17],"p",{},"Both of your current Supabase keys start with ",[14,15,16],"code",{},"eyJhbGci",". The public one and the all-powerful admin one look identical at a glance, which is exactly why the admin key keeps ending up in frontend bundles without anyone noticing.",[10,19,20,21,24,25,28,29,24,32,35,36,39,40,42],{},"That changes with the new key format. Supabase is replacing the legacy ",[14,22,23],{},"anon"," and ",[14,26,27],{},"service_role"," JWTs with ",[14,30,31],{},"sb_publishable_...",[14,33,34],{},"sb_secret_...",". Those prefixes are the practical win nobody talks about: ",[14,37,38],{},"sb_secret_"," in a deployed JavaScript file, a git diff, or a pull request is instantly, obviously wrong. ",[14,41,16],{}," is not.",[44,45,46],"tldr",{},[10,47,48,49,24,51,53,54,56,57,60,61,56,63,65],{},"Supabase is retiring the legacy ",[14,50,23],{},[14,52,27],{}," JWT keys. ",[14,55,23],{}," becomes ",[14,58,59],{},"sb_publishable_"," (one per project, safe in your frontend) and ",[14,62,27],{},[14,64,38],{}," (several allowed, each individually revocable, server-side only). Both old and new keys work at the same time during migration, and legacy keys stay valid until you disable them yourself. Removal is expected late 2026 but the date is officially TBC. Before you migrate, search your deployed bundle for the admin key, because that's the failure this migration actually exposes.",[67,68],"diagram",{"alt":69,"caption":70,"href":71,"src":72},"The publishable key travels from the browser through a Row Level Security check before reaching the database, while the secret key travels from your server directly to the database with no check","Both keys reach the same database. Only one is checked.","/blog/diagrams/supabase-key-trust-boundary","supabase-key-trust-boundary",[74,75,77],"h2",{"id":76},"what-is-actually-changing","What Is Actually Changing",[10,79,80],{},"Two keys become two key types, with different naming and different revocation behavior.",[82,83,84,103],"table",{},[85,86,87],"thead",{},[88,89,90,94,97,100],"tr",{},[91,92,93],"th",{},"Legacy key",[91,95,96],{},"New key",[91,98,99],{},"Where it belongs",[91,101,102],{},"What it can do",[104,105,106,128],"tbody",{},[88,107,108,118,122,125],{},[109,110,111,113,114,117],"td",{},[14,112,23],{}," (JWT, ",[14,115,116],{},"eyJhbGci...",")",[109,119,120],{},[14,121,31],{},[109,123,124],{},"Browser, mobile app, anywhere public",[109,126,127],{},"Only what your RLS policies allow. One per project.",[88,129,130,136,140,143],{},[109,131,132,113,134,117],{},[14,133,27],{},[14,135,116],{},[109,137,138],{},[14,139,34],{},[109,141,142],{},"Server routes, Edge Functions, CI, cron jobs",[109,144,145],{},"Bypasses every RLS policy. Full read and write on every table.",[10,147,148],{},"The permissions model didn't change. A publishable key is still governed by Row Level Security, and a secret key still ignores RLS completely. What changed is the format, the count, and how you revoke them.",[10,150,151],{},"Supabase announced this in 2024. The tracking discussion was last updated on 2025-11-01, at which point new projects stopped shipping legacy keys at all. If your project was created before that, you still have both.",[153,154,155],"info-box",{},[10,156,157,158,165,166,170,171,176],{},"Timeline, stated precisely because the details matter: Supabase's tracking discussion (",[159,160,164],"a",{"href":161,"rel":162},"https://github.com/orgs/supabase/discussions/29260",[163],"nofollow","discussions/29260",") says removal is expected in ",[167,168,169],"strong",{},"late 2026, TBC",". The ",[159,172,175],{"href":173,"rel":174},"https://supabase.com/docs/guides/getting-started/migrating-to-new-api-keys",[163],"migration guide"," says legacy keys will be deprecated by the end of 2026. No specific day has been announced. Supabase's own phrasing on what happens then: \"You have to migrate to use the new API keys by this point or your app will break.\"",[74,178,180],{"id":179},"why-this-is-a-security-upgrade-not-busywork","Why This Is a Security Upgrade, Not Busywork",[10,182,183],{},"Two things get genuinely better, and both matter more for AI-built apps than for hand-written ones.",[10,185,186,189,190,192,193,195,196,198],{},[167,187,188],{},"Secret keys are individually revocable."," The legacy ",[14,191,27],{}," key is one key for the entire project. If it leaks, you reset it, and every single thing that uses it dies at the same instant: your webhook handler, your cron job, your admin dashboard, the seed script on your laptop, the CI pipeline you set up six months ago and forgot. That fear is why people sit on a known-leaked ",[14,194,27],{}," key for days instead of rotating it. With ",[14,197,38],{}," keys you create one per consumer, so revoking the leaked one takes down exactly one thing while everything else keeps running.",[10,200,201,204,205,208],{},[167,202,203],{},"The prefix is greppable."," This one sounds cosmetic. It isn't. A legacy Supabase key in a bundle is a long base64 blob that looks like every other JWT in the file, including the perfectly harmless anon key and any session token. You can't eyeball it. You have to decode the payload and read the ",[14,206,207],{},"role"," field to know whether you're looking at a public key or a god key.",[10,210,211,213,214,217,218,221],{},[14,212,38],{}," is a literal string. It shows up in ",[14,215,216],{},"Ctrl+F"," in devtools, in ",[14,219,220],{},"grep",", in a code review diff, in a secret scanner's ruleset, and in your own memory when you're skimming a file at 1am. The scanners we run at CheckYourVibe already have to decode JWT payloads to tell the two legacy keys apart. With the new format, that step disappears.",[74,223,225],{"id":224},"check-whether-your-secret-key-is-in-your-frontend","Check Whether Your Secret Key Is in Your Frontend",[10,227,228,229,231,232,236],{},"Do this before you migrate, not after. If the legacy ",[14,230,27],{}," key is already in your deployed bundle, migrating without noticing just means you now have a ",[233,234,235],"em",{},"new"," admin key sitting in the same place.",[238,239,241,246,255,258,299],"step",{"number":240},"1",[10,242,243],{},[167,244,245],{},"Search your live bundle.",[10,247,248,249,251,252,254],{},"Open your deployed app, press F12, go to the Sources or Network tab, and search across all files for ",[14,250,27],{},". Then search for ",[14,253,16],{},". Both should turn up either nothing or only your anon key.",[10,256,257],{},"If you find a JWT and want to know which one it is, paste the middle segment into the console:",[259,260,265],"pre",{"className":261,"code":262,"language":263,"meta":264,"style":264},"language-javascript shiki shiki-themes github-dark","JSON.parse(atob('MIDDLE_SEGMENT_OF_THE_KEY'))\n","javascript","",[14,266,267],{"__ignoreMap":264},[268,269,272,276,280,284,287,290,292,296],"span",{"class":270,"line":271},"line",1,[268,273,275],{"class":274},"sDLfK","JSON",[268,277,279],{"class":278},"s95oV",".",[268,281,283],{"class":282},"svObZ","parse",[268,285,286],{"class":278},"(",[268,288,289],{"class":282},"atob",[268,291,286],{"class":278},[268,293,295],{"class":294},"sU2Wk","'MIDDLE_SEGMENT_OF_THE_KEY'",[268,297,298],{"class":278},"))\n",[10,300,301,302,305,306,309],{},"A ",[14,303,304],{},"\"role\": \"anon\""," result is fine. A ",[14,307,308],{},"\"role\": \"service_role\""," result means your admin key is public right now, and you should stop reading about migration and go fix that first.",[238,311,313,318,329],{"number":312},"2",[10,314,315],{},[167,316,317],{},"Check for the wrong prefix on the right variable.",[10,319,320,321,324,325,328],{},"This is the single most common way it happens in Lovable, Bolt, and v0 projects. Anything prefixed ",[14,322,323],{},"NEXT_PUBLIC_"," or ",[14,326,327],{},"VITE_"," gets compiled into the browser bundle by design. If an admin key got assigned to one of those, the bundler did exactly what it was told.",[259,330,334],{"className":331,"code":332,"language":333,"meta":264,"style":264},"language-bash shiki shiki-themes github-dark","# Any hit here is a problem\ngrep -rn \"NEXT_PUBLIC_SUPABASE_SERVICE_ROLE\\|VITE_SUPABASE_SERVICE_ROLE\" .\ngrep -rn \"NEXT_PUBLIC_SUPABASE_SECRET\\|VITE_SUPABASE_SECRET\" .\n\n# After migrating, the new prefix makes this trivial\ngrep -rn \"sb_secret_\" src/ app/ components/ pages/\n","bash",[14,335,336,342,356,368,375,381],{"__ignoreMap":264},[268,337,338],{"class":270,"line":271},[268,339,341],{"class":340},"sAwPA","# Any hit here is a problem\n",[268,343,345,347,350,353],{"class":270,"line":344},2,[268,346,220],{"class":282},[268,348,349],{"class":274}," -rn",[268,351,352],{"class":294}," \"NEXT_PUBLIC_SUPABASE_SERVICE_ROLE\\|VITE_SUPABASE_SERVICE_ROLE\"",[268,354,355],{"class":294}," .\n",[268,357,359,361,363,366],{"class":270,"line":358},3,[268,360,220],{"class":282},[268,362,349],{"class":274},[268,364,365],{"class":294}," \"NEXT_PUBLIC_SUPABASE_SECRET\\|VITE_SUPABASE_SECRET\"",[268,367,355],{"class":294},[268,369,371],{"class":270,"line":370},4,[268,372,374],{"emptyLinePlaceholder":373},true,"\n",[268,376,378],{"class":270,"line":377},5,[268,379,380],{"class":340},"# After migrating, the new prefix makes this trivial\n",[268,382,384,386,388,391,394,397,400],{"class":270,"line":383},6,[268,385,220],{"class":282},[268,387,349],{"class":274},[268,389,390],{"class":294}," \"sb_secret_\"",[268,392,393],{"class":294}," src/",[268,395,396],{"class":294}," app/",[268,398,399],{"class":294}," components/",[268,401,402],{"class":294}," pages/\n",[238,404,406,411,418,507],{"number":405},"3",[10,407,408],{},[167,409,410],{},"Search your git history, not just your working tree.",[10,412,413,414,417],{},"Deleting a key from a file doesn't remove it from history. ",[14,415,416],{},"git log -S"," searches every commit for content containing a string:",[259,419,421],{"className":331,"code":420,"language":333,"meta":264,"style":264},"# Did this key value ever exist in any commit?\ngit log -S 'eyJhbGciOiJIUzI1NiI' --oneline --all\n\n# Same check for the new format, going forward\ngit log -S 'sb_secret_' --oneline --all\n\n# And the usual suspects\ngit log --all --full-history -- .env .env.local .env.production\n",[14,422,423,428,448,452,457,472,476,482],{"__ignoreMap":264},[268,424,425],{"class":270,"line":271},[268,426,427],{"class":340},"# Did this key value ever exist in any commit?\n",[268,429,430,433,436,439,442,445],{"class":270,"line":344},[268,431,432],{"class":282},"git",[268,434,435],{"class":294}," log",[268,437,438],{"class":274}," -S",[268,440,441],{"class":294}," 'eyJhbGciOiJIUzI1NiI'",[268,443,444],{"class":274}," --oneline",[268,446,447],{"class":274}," --all\n",[268,449,450],{"class":270,"line":358},[268,451,374],{"emptyLinePlaceholder":373},[268,453,454],{"class":270,"line":370},[268,455,456],{"class":340},"# Same check for the new format, going forward\n",[268,458,459,461,463,465,468,470],{"class":270,"line":377},[268,460,432],{"class":282},[268,462,435],{"class":294},[268,464,438],{"class":274},[268,466,467],{"class":294}," 'sb_secret_'",[268,469,444],{"class":274},[268,471,447],{"class":274},[268,473,474],{"class":270,"line":383},[268,475,374],{"emptyLinePlaceholder":373},[268,477,479],{"class":270,"line":478},7,[268,480,481],{"class":340},"# And the usual suspects\n",[268,483,485,487,489,492,495,498,501,504],{"class":270,"line":484},8,[268,486,432],{"class":282},[268,488,435],{"class":294},[268,490,491],{"class":274}," --all",[268,493,494],{"class":274}," --full-history",[268,496,497],{"class":274}," --",[268,499,500],{"class":294}," .env",[268,502,503],{"class":294}," .env.local",[268,505,506],{"class":294}," .env.production\n",[10,508,509],{},"If any of these return commits, treat the key as compromised regardless of what your current code looks like.",[10,511,512,513,515,516,520],{},"If you found a live ",[14,514,27],{}," key through any of those checks, our ",[159,517,519],{"href":518},"/blog/how-to/fix-supabase-api-key-exposure","Supabase key exposure guide"," covers rotation, git history scrubbing, and the server-side pattern that keeps it from happening again. Come back here afterward.",[74,522,524],{"id":523},"the-migration-in-the-order-that-wont-break-production","The Migration, in the Order That Won't Break Production",[238,526,527,532],{"number":240},[10,528,529],{},[167,530,531],{},"Create the new keys.",[10,533,534,535,538,539,541],{},"Dashboard, then ",[167,536,537],{},"Project Settings > API Keys",". Create the publishable key (you get one) and then create secret keys. Don't create just one secret key out of habit. Make one per consumer: ",[14,540,38],{}," for your server API, another for your Edge Functions, another for CI. That's the entire point of the new format. Name each one after where it lives so a future you knows what breaks if it's revoked.",[238,543,544,549,552,566],{"number":312},[10,545,546],{},[167,547,548],{},"Update server-side environment variables first.",[10,550,551],{},"Server first, because a server running an old key while the client runs a new one is harmless. The reverse is also harmless here (both key sets are live), but server-first keeps the blast radius boring.",[10,553,554,555,558,559,561,562,565],{},"Replace ",[14,556,557],{},"SUPABASE_SERVICE_ROLE_KEY"," with the new ",[14,560,38],{}," value everywhere it exists. Everywhere is longer than you think: production, staging, preview environments, GitHub Actions secrets, the cron job on Railway, the ",[14,563,564],{},".env"," on the laptop you use for seed scripts.",[259,567,569],{"className":331,"code":568,"language":333,"meta":264,"style":264},"# Server-side only. No NEXT_PUBLIC_, no VITE_, ever.\nSUPABASE_URL=https://yourproject.supabase.co\nSUPABASE_SECRET_KEY=sb_secret_...\n",[14,570,571,576,588],{"__ignoreMap":264},[268,572,573],{"class":270,"line":271},[268,574,575],{"class":340},"# Server-side only. No NEXT_PUBLIC_, no VITE_, ever.\n",[268,577,578,581,585],{"class":270,"line":344},[268,579,580],{"class":278},"SUPABASE_URL",[268,582,584],{"class":583},"snl16","=",[268,586,587],{"class":294},"https://yourproject.supabase.co\n",[268,589,590,593,595],{"class":270,"line":358},[268,591,592],{"class":278},"SUPABASE_SECRET_KEY",[268,594,584],{"class":583},[268,596,597],{"class":294},"sb_secret_...\n",[238,599,600,605,611,640],{"number":405},[10,601,602],{},[167,603,604],{},"Update client-side environment variables.",[10,606,607,608,610],{},"Swap the anon key value for the ",[14,609,59],{}," value. The variable name can stay the same if renaming it means touching a lot of files, though renaming is cleaner:",[259,612,614],{"className":331,"code":613,"language":333,"meta":264,"style":264},"# Client-side. Safe to ship, still governed by RLS.\nNEXT_PUBLIC_SUPABASE_URL=https://yourproject.supabase.co\nNEXT_PUBLIC_SUPABASE_PUBLISHABLE_KEY=sb_publishable_...\n",[14,615,616,621,630],{"__ignoreMap":264},[268,617,618],{"class":270,"line":271},[268,619,620],{"class":340},"# Client-side. Safe to ship, still governed by RLS.\n",[268,622,623,626,628],{"class":270,"line":344},[268,624,625],{"class":278},"NEXT_PUBLIC_SUPABASE_URL",[268,627,584],{"class":583},[268,629,587],{"class":294},[268,631,632,635,637],{"class":270,"line":358},[268,633,634],{"class":278},"NEXT_PUBLIC_SUPABASE_PUBLISHABLE_KEY",[268,636,584],{"class":583},[268,638,639],{"class":294},"sb_publishable_...\n",[10,641,642],{},"Redeploy the frontend.",[238,644,646,651],{"number":645},"4",[10,647,648],{},[167,649,650],{},"Verify every environment before you disable anything.",[10,652,653],{},"Log in as a real user. Trigger the flows that hit your admin key: the webhook, the scheduled job, the admin screen. Check your Supabase API logs for 401s. Because both key sets are valid at once, a mistake at this stage is invisible until you disable the legacy keys, which is precisely why this step exists.",[238,655,657,662],{"number":656},"5",[10,658,659],{},[167,660,661],{},"Disable the legacy keys last.",[10,663,664,665,667,668,24,670,672],{},"Once nothing is still calling Supabase with a ",[14,666,16],{}," key, disable the legacy ",[14,669,23],{},[14,671,27],{}," keys in the dashboard. This is a deliberate action you take, not something Supabase does to you on a schedule (at least not until the removal date lands).",[674,675,676],"warning-box",{},[10,677,678,679,682,683,685,686,688],{},"The environments people forget: Vercel and Netlify ",[167,680,681],{},"preview deployments"," often carry their own copy of environment variables, separate from production. So do staging projects, GitHub Actions secrets, Supabase Edge Function secrets, and any local ",[14,684,564],{}," a teammate is running scripts against. Disable the legacy keys while one of those still holds an old value and that thing starts returning 401s with no obvious cause. Grep your infra for ",[14,687,16],{}," before you flip the switch.",[74,690,692],{"id":691},"what-to-do-about-the-date","What to Do About the Date",[10,694,695],{},"Don't wait for a deadline that hasn't been set. The honest status right now: removal is expected late 2026, Supabase marks it TBC, and the docs say deprecated by end of 2026. That's it. Anyone quoting you a specific day is guessing.",[10,697,698,699,703],{},"Subscribe to ",[159,700,702],{"href":161,"rel":701},[163],"the tracking discussion",". It's where the date will be confirmed. In the meantime, the migration itself takes an afternoon on a small app and leaves you with revocable secret keys and a key format you can actually search for, which is worth doing on its own.",[705,706,707,723,733,745,751],"faq-section",{},[708,709,711],"faq-item",{"question":710},"Is sb_publishable the same as the anon key?",[10,712,713,714,716,717,719,720,722],{},"Functionally, yes. ",[14,715,59],{}," is the direct replacement for the legacy anon key. It's meant to sit in your frontend, it identifies your project, and what it can read or write is still controlled entirely by your Row Level Security policies. The difference is the format: the anon key is a JWT starting with ",[14,718,16],{},", while the publishable key is a short opaque string starting with ",[14,721,59],{},". You get one per project.",[708,724,726],{"question":725},"When exactly do the old Supabase keys stop working?",[10,727,728,729,732],{},"There's no fixed date yet. The Supabase tracking discussion says removal is expected in late 2026 and explicitly marks it TBC, and the migration docs say legacy keys will be deprecated by the end of 2026. Until you disable them yourself, your legacy keys keep working alongside the new ones. Watch ",[159,730,164],{"href":161,"rel":731},[163]," for the confirmed date.",[708,734,736],{"question":735},"What replaces the service_role key?",[10,737,738,739,741,742,744],{},"Secret keys with the ",[14,740,38],{}," prefix. The meaningful difference is that you can create several and revoke any one of them without touching the others. The legacy ",[14,743,27],{}," key was a single key for the whole project, so rotating it meant every service using it had to be updated in the same moment.",[708,746,748],{"question":747},"Can I use the old and new Supabase keys at the same time?",[10,749,750],{},"Yes, and that's the intended path. Both key sets are valid simultaneously. Move each environment to the new keys one at a time, verify it, and disable the legacy keys only when nothing is still using them.",[708,752,754],{"question":753},"Do I have to migrate if my app already works fine?",[10,755,756],{},"Eventually, yes. Supabase's migration guide puts it plainly: \"You have to migrate to use the new API keys by this point or your app will break.\" New projects already ship without legacy keys. Migrating early also gets you individually revocable secret keys instead of one unrevocable admin key, which is a real improvement.",[758,759,760,765,770],"related-articles",{},[761,762],"related-card",{"description":763,"href":518,"title":764},"Found a service_role key in your bundle? Rotate it, scrub it from git history, and move it server-side.","How to Fix Supabase API Key Exposure",[761,766],{"description":767,"href":768,"title":769},"RLS is what makes your publishable key safe to expose. Step-by-step policy setup and testing.","/blog/how-to/setup-supabase-rls","How to Set Up Supabase Row Level Security",[761,771],{"description":772,"href":773,"title":774},"RLS, auth configuration, and API protection patterns for a production Supabase backend.","/blog/best-practices/supabase","Supabase Security Best Practices",[776,777,780],"cta-box",{"href":778,"label":779},"/","Start Free Scan",[10,781,782],{},"Scan your deployed app for Supabase admin keys in your JavaScript bundle, tables missing RLS, and open policies. Free, no signup required.",[784,785,786],"style",{},"html pre.shiki code .sDLfK, html code.shiki .sDLfK{--shiki-default:#79B8FF}html pre.shiki code .s95oV, html code.shiki .s95oV{--shiki-default:#E1E4E8}html pre.shiki code .svObZ, html code.shiki .svObZ{--shiki-default:#B392F0}html pre.shiki code .sU2Wk, html code.shiki .sU2Wk{--shiki-default:#9ECBFF}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html pre.shiki code .sAwPA, html code.shiki .sAwPA{--shiki-default:#6A737D}html pre.shiki code .snl16, html code.shiki .snl16{--shiki-default:#F97583}",{"title":264,"searchDepth":344,"depth":344,"links":788},[789,790,791,792,793],{"id":76,"depth":344,"text":77},{"id":179,"depth":344,"text":180},{"id":224,"depth":344,"text":225},{"id":523,"depth":344,"text":524},{"id":691,"depth":344,"text":692},"how-to","2026-07-31","Supabase is replacing anon and service_role with sb_publishable_ and sb_secret_ keys. How to migrate, and how to check if your secret key is in your bundle.",false,"md",[800,802,804,806,808],{"question":710,"answer":801},"Functionally, yes. The sb_publishable_ key is the direct replacement for the legacy anon key. It's meant to sit in your frontend, it identifies your project, and what it can actually read or write is still controlled entirely by your Row Level Security policies. The difference is the format: the anon key is a JWT starting with eyJhbGci, while the publishable key is a short opaque string starting with sb_publishable_. You get one publishable key per project.",{"question":725,"answer":803},"There is no fixed date yet. Supabase's tracking discussion says removal is expected in late 2026 and marks it TBC, and the migration docs say legacy keys will be deprecated by the end of 2026. Until you disable them yourself, your legacy anon and service_role keys keep working alongside the new ones. Watch https://github.com/orgs/supabase/discussions/29260 for the date to be confirmed.",{"question":735,"answer":805},"Secret keys with the sb_secret_ prefix. The big difference is that you can create several of them and revoke any single one without touching the others. The legacy service_role key was a single key for the whole project, so rotating it meant updating every service that used it at the same moment.",{"question":747,"answer":807},"Yes, and that's the intended migration path. Both key sets are valid simultaneously. Move each environment over to the new keys one at a time, verify it works, and only disable the legacy keys once nothing is still using them.",{"question":753,"answer":809},"Eventually, yes. Supabase's own wording in the migration guide is blunt: 'You have to migrate to use the new API keys by this point or your app will break.' New projects have already stopped shipping legacy keys. Migrating early also gets you individually revocable secret keys, which is a real security improvement over the single service_role key.","yellow",null,"supabase service_role key replacement, sb_publishable vs anon key, supabase new api keys, sb_secret key, supabase api key migration, supabase legacy jwt keys",{},"The new sb_secret_ prefix makes a leaked Supabase admin key greppable. Here's the migration order, and how to search your own bundle first.","/blog/how-to/supabase-new-api-keys","8 min read","[object Object]","HowTo",{"title":5,"description":796},{"loc":815},"blog/how-to/supabase-new-api-keys",[],"summary_large_image","uri3LdhwGWZc1PnK1S9RBxlt8iDVnTYO3bBXDjjWQ2k",1785543184230]