API keys in the frontend: a published credential, not a secret
A key in a page's JavaScript is public the moment the page loads. How it happens, how to check your site in two minutes, and the fix.
The short answer
Everything a browser downloads to show a page is public: the HTML, every script, every configuration value baked into those scripts at build time. Anyone can open the developer tools and read it, and automated scanners do exactly that across the whole web, harvesting anything that looks like a key. An API key placed in front-end code is therefore not a secret; it is a published credential. We look for one on every site we audit, and the fix is a simple pattern: the browser talks to your own function, and the function, which can keep secrets, talks to the service.
How it happens
| Path | What it looks like |
|---|---|
| Convenience | A developer calls the email, CRM or AI service directly from the page during a prototype, and it ships |
| Build substitution | A configuration value referenced in front-end code is inlined into the built JavaScript as a plain string |
| Theme and plugin settings | An API key pasted into a settings field that renders into every page |
| Misread documentation | A provider’s example uses a key in the browser for a demo; the site copies it for production use |
| Public key without restrictions | A key intended to be public, but never restricted to a domain or to read-only operations |
The two-minute check
- Open your site, view the page source, and search for the names of services you use: email provider, CRM, maps, payments, AI.
- Open the network tab, reload, and look at the scripts loaded. Search inside them for key-shaped strings: long sequences of letters and digits, especially with provider prefixes.
- For anything found, check at the provider what that key can do and whether it is restricted.
- Repeat on a preview or staging address if you have one; keys leak there too.
The pattern that keeps keys where they belong
The browser never holds a secret. When the page needs something a service provides, sending an email, creating a record, asking an AI, it calls a small function on your own platform. That function holds the key in encrypted configuration, validates the request, applies rate limits, calls the service, and returns only what the page needs. Public keys, where a provider designed them for the browser, stay there with domain and operation restrictions set at the provider.
What this means for you
Do the two-minute check on your own site today. If you find a key, assume it is already known: rotate it, move the call behind a function, revoke the old one, and check the provider’s usage. Then make it a rule: secrets exist only in server-side configuration, the browser gets public restricted keys or none, and every audit repeats the check. It is a small discipline that prevents a disproportionately large class of incidents.
Frequently asked questions
The key is only for a maps or analytics widget. Does it matter?
Some providers issue keys designed to be public, restricted to your domain and to specific read-only operations. Those are fine in the page, provided the restrictions are actually set. A key that can send email, charge cards, read customer data or run up a bill must never be there. Check what each exposed key can do, not what it is used for.
We found a key in our site's JavaScript. What now?
Treat it as compromised. Create a new key at the provider, move the call behind a function that holds the key server-side, deploy, then revoke the old key. Check the provider's usage logs for activity you did not cause. Scanners harvest exposed keys within minutes of them appearing, so do this today, not next sprint.
Is this only a problem on custom-built sites?
No. Page builders and plugins embed keys in pages routinely, and site owners paste API keys into theme settings that render them into every page. The check is the same: look at what the browser receives. The stack does not matter; the pattern does.
Sources
- OWASP Cheat Sheet Series: Secrets Management (accessed 2026-09-11)