Security basics
How to use secrets in Replit, and what still gets published
How to use secrets in Replit: add one, read it back, and fix the two reasons it comes back undefined. Plus the keys the Secrets tool cannot keep private.

In short
- How to use secrets in Replit: open the Secrets tool, add a name and a value, and read it back in your code as an environment variable.
- That keeps the key out of your files. It does not keep it out of your published app, because a value your browser code reads is built into what every visitor downloads.
- The tell is the name. A variable beginning VITE_ or NEXT_PUBLIC_ was deliberately put in the browser by your build tool.
- A secret that reads as empty in the published app usually means the live version was published before you added it. Publish again.
Somewhere between building your app and publishing it, Replit told you to stop putting your API key in the code. So you looked up how to use secrets in Replit, moved the key into the Secrets tool, and the warning went away. Then something told you the key is still visible in your published app, and both of those things seem to be true at the same time.
They are. Here is the part most advice about this leaves out: the Secrets tool decides where a value is kept. Your code decides where it is carried. Those are two separate questions, and only the first one has anything to do with the tool.
Replit is where this bites hardest, because the half of your app that runs on Replit's machine and the half that runs on your visitor's laptop sit in the same project, often in adjacent files. Nothing in the editor draws a line between them.
Between 12 and 14 August 2026 we ran the same nine external checks over 30,998 live apps, of which 3,042 were published on Replit. In 219 of those Replit apps something key-shaped was sitting in the code a visitor downloads. Across the whole sample most of what that check finds is a Google key, which is usually fine once it has been restricted. The ones that are not fine are the ones a secrets manager was supposed to prevent. The full numbers are in our scan report.
How do I use secrets in Replit?
Open the Secrets tool, add a name and a value, and read the value back in your code as an environment variable. It takes about a minute.
- In your project, open Secrets. It is in the tools list, and searching the tool pane for the word finds it.
- Choose + New secret. Give it a name in capitals with underscores, like
OPENAI_API_KEY. That name is what your code will use, so it matters more than it looks. - Paste the value into the second field and save. Replit encrypts it and keeps it outside your project files.
- Read it in your code. In JavaScript that is
process.env.OPENAI_API_KEY, and in Python it isos.getenv("OPENAI_API_KEY"). - Go back and delete the value from wherever it was before.
Step five is the one people skip, and it is the one that decides whether any of this helped. Adding a secret does not remove the copy you already had. A key pasted into a file last week is still in that file, still in your project history, and still inside every copy of your app that has been published since.
A .env file does the same job as the Secrets tool, with one difference that
matters: it is a file, so it travels with the project when somebody forks it or
connects it to a repository.
Are Replit Secrets safe?
For the job they do, yes. The value is encrypted, it is kept out of your source, and the three commonest ways a key gets away from someone are all closed by that: you share your project with a collaborator, you connect it to a repository, or somebody watches you work.
Think of it as a locked drawer. Anything in the drawer is out of sight of a person reading your files. What the drawer cannot decide is what your app does with the contents once your own code has opened it and walked off.
And a published Replit app walks off with quite a lot. Every visitor who loads your site is sent the entire front half of it, because a browser cannot draw a page it was not sent. If the code that opens the drawer is code that gets sent to visitors, the value it took out travels with it.
Which half of your app reads the key?
The half that runs on Replit's machine can read a secret safely. The half that runs in your visitor's browser cannot, and the usual way of making it work is also the way the key becomes public.
Your server code is the part Replit runs: an Express route, a Python handler, a function that talks to OpenAI or Stripe and sends your app back an answer. It reads a secret, uses it, and the value never leaves the machine.
Your browser code is everything the visitor's laptop runs. In a React project that is most of what you have been editing. It is compiled into a bundle of JavaScript and downloaded, in full, by everyone who opens your site.
process.env does not exist in a browser, so browser code reading it gets
nothing at all. To make the value arrive, somebody renames it:
VITE_OPENAI_API_KEY in a Vite project, or NEXT_PUBLIC_OPENAI_API_KEY in a
Next.js one. It works immediately, because that prefix is an instruction to the
build tool to write the value into the bundle. Vite's own documentation says so
in as many words, and warns against putting API keys in one for exactly that
reason.
So the fastest check in this article is a search. Open your project and look for
VITE_ and NEXT_PUBLIC_. Every match is a value that your build tool has been
told to publish.
That is correct for some of them. A Supabase publishable key is designed to sit in a browser, and so is a Google Maps key with a referrer restriction on it. It is wrong for anything that spends money or reads a database without asking who is knocking, and telling the two apart takes about a minute per key.
If you would rather see what your published app is handing out before you go file by file, our free scan reads your live site from the outside and tells you what it can find there. It takes about 20 seconds and needs no account: scan your app.
Why is my Replit secret not working?
Two reasons, and from where you are sitting they produce the same symptom: an empty value and an app that does not work.
The code reading it runs in the browser. There is no environment there to
read, so process.env.YOUR_KEY is empty and always will be. This one is not a
configuration problem and no amount of re-adding the secret will move it. The
call that needs the key has to move to the server half of your app.
Your published app is running an older version. Replit keeps two sets of values, the ones in your workspace and the ones your published app runs on. They synchronise, so a secret you add normally reaches the deployment. What the live app is actually using, though, is whatever was there the last time you published it. Add a secret afterwards and the running app knows nothing about it until you publish again.
Replit's own troubleshooting for an app that works in the editor and breaks when
published starts in exactly this place, so it is worth opening the deployment
secrets and reading the names before assuming anything is broken. A name spelled
OPENAI_KEY in one place and OPENAI_API_KEY in the other produces the same
empty value as a missing secret.
A deploy that fails at midnight is when the shortcut gets taken. Pasting the value straight into the code unblocks it in seconds, the app comes back, and the key is in your published bundle from that moment on.
The key is already in my published app. What now?
Rotate it, before you change any code. In the provider's dashboard, generate a new key and revoke the old one.
That order matters because rotation is the only step that makes the exposed value stop working. Editing your code removes it from the current version and leaves it in your project history, and it does nothing at all about the copies of your bundle that have already been downloaded, cached and crawled. Your app being small does not help either: automated crawlers read public sites for key-shaped strings continuously, with no idea who you are.
Then, in this order:
- Put the new key in Secrets and read it from server code only.
- Move the call that needed it. Anything talking to OpenAI, Anthropic, Stripe or your database with an administrative key belongs behind a route your app calls, so the browser asks your server and your server holds the key.
- Check the provider's usage and billing pages for the period the old key was public. Rotation stops what happens next and says nothing about what already happened.
Model-provider keys are the ones to check first on Replit. OPENAI_API_KEY is
the example Replit's own documentation reaches for when it shows you how to add
a secret, and there is no publishable variant of an OpenAI or Anthropic key.
Every one of them bills your account directly.
One more place to look while you are here: if your project publishes source maps, a visitor can read that bundle as the original files you wrote, with your own variable names still on them.
What to do this week
What to do
- Move every key into the Secrets tool, then delete the copies you left behind in files. Adding a secret does not remove the old value.
- Search your project for
VITE_andNEXT_PUBLIC_. Each match is a value your build tool publishes on purpose, and each one needs to be a key that was safe to publish. - For any key that is not, move the call that uses it to your server half, so the browser asks your app and your app holds the key.
- If a secret reads as empty in the published app but works in the editor, publish again and check the name in your deployment secrets before changing code.
- Rotate anything that has already gone out, at the provider, before you touch the code. Then read the billing page for the weeks it was public.
Do the search first. It takes a minute, it needs nothing installed, and it tells you which of the keys in your project are already public. The 10-minute security checklist covers the rest of what is worth confirming on a newly launched app, and the plain-language walkthrough for this platform is is your Replit app safe.
FAQ
Are Replit Secrets safe?
Yes, for the job they do. Replit encrypts the values and keeps them out of your files, so sharing your project, publishing it to a repository or streaming yourself working on it no longer hands the key to anyone watching. What the tool cannot do is decide where your app carries the value afterwards. A key read by code that runs on Replit stays on Replit. The same key read by code that runs in your visitor's browser is built into what you publish, and storing it in Secrets first changes nothing about that.
Why is my Replit secret undefined?
Two causes, and they look the same from where you are standing. Either the code reading it runs in the browser, where there is no environment to read and process.env has nothing in it, or your published app is running a version that was deployed before you added the secret. For the second, publish again: the values a live app is using are the ones that were there when it last went out.
Can I use a secret in my React frontend on Replit?
You can put a value there, and it will not be secret. React code runs on your visitor's machine, so anything it reads has to be sent to that machine first. Build tools make this explicit with a prefix: Vite only exposes variables beginning VITE_ to browser code, and Next.js only exposes NEXT_PUBLIC_ ones. Adding the prefix is how people make a key work in the frontend, and it is also the moment the key becomes public. Publishable keys belong there. Anything that spends money or reads a database belongs on the server.
Do I need to add my secrets again when I publish?
Usually not, because deployment secrets synchronise with your workspace ones, but the value your live app is using is the one that was present at your last publish. A secret added after that reaches the deployment on the next one. Replit's own troubleshooting for an app that works in the editor and fails when published starts here, so if something went missing at deploy time, open the deployment secrets and check the name is there and spelled the same way.
I pasted an API key into a file before I found the Secrets tool. Is deleting the file enough?
No. Rotate the key at the provider first, which is what actually closes the door, then move the value into Secrets. Deleting a line of code removes it from the current version and not from your project history, and not from any copy of your published app that someone already has. Rotation is the only step that makes the old value stop working, and it usually takes about a minute in the provider dashboard.