[{"data":1,"prerenderedAt":422},["ShallowReactive",2],{"blog-en-google-api-key-exposed-in-frontend":3},{"id":4,"title":5,"body":6,"category":380,"cover":381,"coverAlt":381,"description":382,"draft":383,"extension":384,"faq":385,"image":401,"keywords":402,"meta":409,"navigation":410,"ogTitle":411,"path":412,"published":413,"seo":414,"stem":415,"tldr":416,"updated":413,"__hash__":421},"blog_en\u002Fblog\u002Fgoogle-api-key-exposed-in-frontend.md","Is a Google API key exposed in your frontend a problem?",{"type":7,"value":8,"toc":368},"minimark",[9,18,26,35,40,43,46,49,52,56,59,66,69,75,79,89,141,148,151,158,162,260,263,268,272,275,282,292,296,299,305,308,311,321,325,356],[10,11,12,13,17],"p",{},"Open your app in a browser, view the page source, and search for ",[14,15,16],"code",{},"AIza",". If a\nforty-character string comes back, someone has probably already told you it is\n\"exposed\", and probably did not say what to do next.",[10,19,20,21,25],{},"Here is the part that most advice on this gets wrong: ",[22,23,24],"strong",{},"a Google API key in your\nfrontend is not the same kind of problem as a Stripe secret key in your frontend,\nand the fix is not to hide it."," Treating the two the same way means either\npanicking about your map or, worse, learning to shrug at the word \"exposed\"\nbefore the day it matters.",[10,27,28,29,34],{},"Between 12 and 14 August 2026 we ran the same nine external checks over 30,998\nlive apps built with Lovable, Bolt, v0, Replit and Base44. A Google API key\nturned up in 1,142 of them. A Stripe secret key turned up in three. The full\nnumbers are in ",[30,31,33],"a",{"href":32},"\u002Fresearch\u002Fvibe-coded-app-security-2026","our scan report",".",[36,37,39],"h2",{"id":38},"is-it-bad-that-my-google-api-key-is-exposed-in-the-frontend","Is it bad that my Google API key is exposed in the frontend?",[10,41,42],{},"Usually not. It depends entirely on whether the key is restricted.",[10,44,45],{},"A Google API key is not a password. It is closer to an account number: it tells\nGoogle which project to bill and which quota to draw down. On its own it says\nnothing about who is making the request.",[10,47,48],{},"That is why it can live in your page. The map is drawn by your visitor's browser,\nso the browser is what has to send the account number to Google. There is no\nversion of this where the map loads and the number stays hidden.",[10,50,51],{},"The protection comes from a second setting, held on Google's side, that says\nwhich websites the number may be used from. Google calls it an HTTP referrer\nrestriction. With it set, the key printed in your page works on your site and\nreturns an error everywhere else.",[36,53,55],{"id":54},"why-your-app-ships-the-key-at-all","Why your app ships the key at all",[10,57,58],{},"Because the request goes out from your visitor's browser, not from your server.",[10,60,61,62,65],{},"When someone loads a page with a map on it, that browser talks to\n",[14,63,64],{},"maps.googleapis.com"," directly. It has to say which Google project the request\nbelongs to, and that identifier has to be in the page, because the page is where\nthe request is made from.",[10,67,68],{},"Google knows this. It is why the restriction exists as a separate control: the\nidentifier was always going to be public, so the thing that decides who may use\nit lives somewhere the public cannot reach.",[70,71],"diagram",{"alt":72,"caption":73,"src":74},"Two rows. In the top row a restriction stands between the requests and Google: a request from yourapp.com passes it and reaches maps.googleapis.com, while the same key sent from other.example is stopped at the restriction and marked with a cross. In the bottom row the restriction is drawn as a broken line and both requests get through, the stranger's one dashed in amber.","The key is identical in both rows. The only difference is whether a restriction is set on it.","\u002Fblog\u002Fgoogle-api-key-exposed-in-frontend\u002Frestricted-or-not-1600x820.png",[36,76,78],{"id":77},"how-to-restrict-a-google-api-key","How to restrict a Google API key",[10,80,81,82,88],{},"It is free, it is about five minutes, and you do not need to touch your app's\ncode. In the ",[30,83,87],{"href":84,"rel":85},"https:\u002F\u002Fconsole.cloud.google.com\u002F",[86],"nofollow","Google Cloud console",":",[90,91,92,100,110,129,138],"ol",{},[93,94,95,96,99],"li",{},"Go to ",[22,97,98],{},"APIs & Services → Credentials"," and click the key you found.",[93,101,102,103,106,107,34],{},"Under ",[22,104,105],{},"Application restrictions",", choose ",[22,108,109],{},"Websites",[93,111,112,113,116,117,120,121,124,125,128],{},"Add your domain. For a site at ",[14,114,115],{},"yourapp.com",", add ",[14,118,119],{},"https:\u002F\u002Fyourapp.com\u002F*",".\nAdd your preview or staging domain too, if you have one, and remember the\n",[14,122,123],{},".lovable.app"," or ",[14,126,127],{},".vercel.app"," address your app was published on before you\nattached a custom domain.",[93,130,102,131,106,134,137],{},[22,132,133],{},"API restrictions",[22,135,136],{},"Restrict key"," and tick only the APIs\nyour app actually calls. A map with places search needs the Maps JavaScript\nAPI and the Places API, and nothing else.",[93,139,140],{},"Save, wait a few minutes, then load your own site and check the map still\ndraws.",[10,142,143,144,147],{},"If step 3 is wrong, you find out immediately and visibly: the map fails with\n",[14,145,146],{},"RefererNotAllowedMapError"," in the browser console, which names the exact\nproblem. Add the missing domain and it works again. This is a reversible change,\nwhich is why it is worth doing before you have finished reading about it.",[10,149,150],{},"One thing not to do: Google's own troubleshooting guidance says IP address\nrestrictions are for server-side keys and are not suitable for a key used by a\nbrowser. If you restrict a frontend key by IP, your app breaks for every visitor.",[10,152,153,154,34],{},"If you would rather see which keys your app is actually shipping before you go\nkey by key, our free scan reads your live site from outside and tells you what it\ncan find. It takes about 20 seconds and needs no account:\n",[30,155,157],{"href":156},"\u002F#scan","scan your app",[36,159,161],{"id":160},"which-google-credentials-belong-in-a-browser","Which Google credentials belong in a browser",[163,164,165,184],"table",{},[166,167,168],"thead",{},[169,170,171,175,178,181],"tr",{},[172,173,174],"th",{},"Credential",[172,176,177],{},"What it looks like",[172,179,180],{},"In your frontend?",[172,182,183],{},"What actually protects it",[185,186,187,208,224,242],"tbody",{},[169,188,189,193,198,205],{},[190,191,192],"td",{},"Maps or Places browser key",[190,194,195],{},[14,196,197],{},"AIza…",[190,199,200],{},[201,202,204],"key-verdict",{"type":203},"safe","Belongs here",[190,206,207],{},"The referrer restriction, plus a quota cap",[169,209,210,213,217,221],{},[190,211,212],{},"Firebase web config key",[190,214,215],{},[14,216,197],{},[190,218,219],{},[201,220,204],{"type":203},[190,222,223],{},"Firebase Security Rules and App Check",[169,225,226,229,233,239],{},[190,227,228],{},"A key you use from your server",[190,230,231],{},[14,232,197],{},[190,234,235],{},[201,236,238],{"type":237},"danger","Never",[190,240,241],{},"Nothing, once it is public. Keep it server-side",[169,243,244,247,253,257],{},[190,245,246],{},"Service account",[190,248,249,250],{},"a JSON file containing ",[14,251,252],{},"\"private_key\"",[190,254,255],{},[201,256,238],{"type":237},[190,258,259],{},"Nothing. This one is a real credential",[10,261,262],{},"The first three rows are the same string. A Google API key does not say on its\nfront what it is for, so the question is never \"what does this key look like\" but\n\"what is this key allowed to do\", and that answer lives in the console rather\nthan in your code.",[70,264],{"alt":265,"caption":266,"src":267},"A single AIza key at the top branches into three identical copies. The first is ticked and labelled yourapp.com, the second is ticked and labelled firebaseConfig, and the third is crossed and labelled with an asterisk standing for any website. Separated below by a dashed rule, a fourth item reading type service_account is crossed.","Three of these are the same forty characters. What separates them is a setting you cannot see from the page source.","\u002Fblog\u002Fgoogle-api-key-exposed-in-frontend\u002Fone-shape-three-jobs-1600x720.png",[36,269,271],{"id":270},"the-firebase-key-is-meant-to-be-public","The Firebase key is meant to be public",[10,273,274],{},"This is the row people ask about most, and Firebase's own documentation answers\nit plainly: API keys for Firebase services are safe to include in code or\nconfiguration files, because they do not control access to anything. Firebase\nSecurity Rules and App Check decide that.",[10,276,277,278,34],{},"If that sounds familiar, it is the same arrangement Supabase uses. The\npublishable key identifies the project, and the rules behind it decide row by row\nwho may read what. In both cases the key is safe only because of those rules, so\nthe rules are the thing to go and look at:\n",[30,279,281],{"href":280},"\u002Fblog\u002Fsupabase-rls-on-but-table-still-public","switching them on is not the same as being protected",[283,284,286],"callout",{"type":285},"warn",[10,287,288,291],{},[22,289,290],{},"The one Firebase trap, and it is new."," Firebase's documentation is specific\nabout it: do not add the Gemini Developer API, listed as the Generative Language\nAPI, to the allowlist of your Firebase web key. That key is published on purpose,\nand every model call made with it bills your project. It is an easy mistake to\nmake in an app that started as a Firebase app and later grew an AI feature, which\nby now is a great many of them.",[36,293,295],{"id":294},"what-actually-happens-when-an-unrestricted-key-leaks","What actually happens when an unrestricted key leaks",[10,297,298],{},"A bill, or an outage, or both. Not a data breach.",[10,300,301,302,304],{},"Automated scrapers crawl public pages collecting ",[14,303,16],{}," strings, and an\nunrestricted key works from anywhere, so yours ends up serving map tiles and\nplace lookups for somebody else's project. Google names this directly in its own\ngetting-started documentation: the reason to set restrictions is to prevent quota\ntheft.",[10,306,307],{},"You usually notice it in one of two ways. The invoice is larger than last month\nfor no reason you can point to, or your app's maps stop drawing partway through\nthe month because the project has run through its quota and every visitor now\nsees a grey box.",[10,309,310],{},"Both are recoverable. Neither exposes your users' data, because a Maps key cannot\nread your database. That is the honest difference between this finding and a\nleaked database key, and it is why our scanner reports a Google API key as\nsomething to check rather than as an emergency.",[10,312,313,314,317,318,320],{},"The one Google credential that does behave like an emergency is a service\naccount. If your app is shipping a JSON file with ",[14,315,316],{},"\"type\": \"service_account\""," and\na ",[14,319,252],{}," block in it, that is a real credential with real permissions on\nyour cloud project. Delete the key in the console, then look at what it could\nreach.",[36,322,324],{"id":323},"what-to-do-right-now","What to do right now",[326,327,328],"key-takeaways",{},[329,330,331,337,343,346,349],"ul",{},[93,332,333,334,336],{},"Search your live site's page source for ",[14,335,16],{},". Every match is a key to account for.",[93,338,339,340,342],{},"Restrict each one in the Google Cloud console: ",[22,341,109],{}," under Application restrictions, your domain, and only the APIs you use. Free, about five minutes, no code change.",[93,344,345],{},"Set a daily quota cap on the key while you are in there. A restriction reduces the traffic; a cap limits what any of it can cost.",[93,347,348],{},"Leave your Firebase config key where it is. Check instead that your Security Rules are doing the work, and keep the Gemini API off that key's allowlist.",[93,350,351,352,355],{},"If you find a ",[14,353,354],{},"service_account"," JSON file in your frontend, treat it as urgent. Delete the key in the console rather than only removing the file, because your version history still has the old value.",[10,357,358,359,363,364,34],{},"If you would rather work through this as a list, the\n",[30,360,362],{"href":361},"\u002Fchecklist","10-minute security checklist"," covers this alongside the other things\nworth turning off in a newly launched app. And for the wider question of which\nkeys belong in a browser at all, we have\n",[30,365,367],{"href":366},"\u002Fblog\u002Fwhich-api-keys-are-safe-in-your-frontend","a guide to telling publishable keys from secret ones",{"title":369,"searchDepth":370,"depth":370,"links":371},"",3,[372,374,375,376,377,378,379],{"id":38,"depth":373,"text":39},2,{"id":54,"depth":373,"text":55},{"id":77,"depth":373,"text":78},{"id":160,"depth":373,"text":161},{"id":270,"depth":373,"text":271},{"id":294,"depth":373,"text":295},{"id":323,"depth":373,"text":324},"Security basics",null,"A Google API key exposed in your frontend is the key our scanner finds most often, and usually it is fine. One free setting decides which it is.",false,"md",[386,389,392,395,398],{"q":387,"a":388},"Someone found my Google Maps key in the page source. Should I rotate it?","Restrict it first, which takes about five minutes and does not break anything. Open the key in the Google Cloud console, set Application restrictions to Websites and add your domain, then set API restrictions to only the APIs your app uses. Rotating comes after, and only if the key was unrestricted for a while and you can see usage you did not cause.",{"q":390,"a":391},"Can I just hide the key so nobody finds it?","No. Your visitor's browser has to send the key to Google to load the map, so anything the browser can use, a visitor can read. Minifying or renaming only slows someone down for a few seconds. Restricting the key is the fix that actually works, and it works whether or not anyone finds the key.",{"q":393,"a":394},"My key is restricted to my domain. Can someone still use it?","A referrer header can be forged, so a determined person can send requests that claim to come from your site. What the restriction removes is the easy case: an automated scraper that finds keys on public pages and uses them straight from its own server. That is the traffic that produces surprise bills, so the restriction is worth setting even though it is not a wall. Set a daily quota cap on the key as well, and you have a ceiling on what any of it can cost you.",{"q":396,"a":397},"Firebase put an API key in my config file. Is that a mistake?","No, and Firebase documents it. A Firebase web API key identifies your project rather than authorising access, and access is decided by Firebase Security Rules and App Check instead. It is meant to be in your code. The one thing to avoid is adding the Gemini Developer API to that key's allowlist, because that turns a key you publish on purpose into one that can spend money.",{"q":399,"a":400},"What if I find a file with \"type\": \"service_account\" in my app?","That is a different situation and it is urgent. A service account key is a real credential with a private key inside it, and it belongs on a server. Delete the key in the Google Cloud console under IAM and Admin, then Service Accounts, take the file out of your frontend, and check your billing and audit logs. Deleting it from your code alone does not close the door, because the old value still exists in your version history.","\u002Fblog\u002Fgoogle-api-key-exposed-in-frontend\u002Fcard-800x500.png",[403,404,405,406,407,408],"google api key exposed frontend","restrict google maps api key","http referrer restriction","firebase api key public","AIza api key","is my google api key safe",{},true,"Google API key exposed in your frontend?","\u002Fblog\u002Fgoogle-api-key-exposed-in-frontend","2026-08-22",{"title":5,"description":382},"blog\u002Fgoogle-api-key-exposed-in-frontend",[417,418,419,420],"A Google API key exposed in your frontend is usually fine, because a Google key is a billing identifier and not a password.","What makes it fine is an HTTP referrer restriction: a setting that tells Google the key only works from your own website.","It is free, it takes about five minutes, and it is the one thing worth doing today.","A Google service account file in your app is the different and urgent case. That one is a real credential.","uOyjpYR0PCCv53gYTszqnv5zgGbRjK5NGGBXzdnUo5E",1787826048204]