Cypress v15.10.0 has announced a big switch coming in v16 - the new way of dealing with environment values and secrets. Let's see why this change is necessary, what is means for your testing code, and what the best practices for handling secrets in your tests should be.
First, let me say: Cypress end-to-end browser tests run in the browser. This is different from Playwright and different from how other test runners execute tests. When you run cypress open or cypress run, the Cypress Electron-based binary starts the Node process, launches the browser, loads the e2e spec into the browser, and lets it run. So let's imagine we have a few environment variables that we are going to use during the test:
1 | import { defineConfig } from 'cypress' |
The above env block passes variables to be used by the test, which could be something like this:
1 | it('logs in', () => { |
Ok, great, so where are these values "username", "password", and "apiToken"? Are they only in the Node process, the browser process, or both? In Cypress v15, these values are in both! Here is how you can visualize where the variables are:

Hmm, ok, so the spec has all these values. What about the application under test? What about every 3rd-party script loaded by the web app under test? Turns out, every app under Cypress test can access everything inside the global Cypress object:
1 | if (window.Cypress) { |
The above piece of code inside the website will print the username, the password, and the api token used by the test runner. Hmm, is this a good idea? I do not know why Cypress sets window.Cypress global object, it could simply set window.Cypress = true to let the app know that it is being tested, but there was no need to set the entire object.
Ok, so there are 3 different types of environment variables I see in our config file.
usernameis something the test must enter into the input field. It does not seem to be very secret, it could be a public value.passwordis something we probably want to hide from the application code, but something the spec running in the browser needs to get at some point.apiTokenis NOT needed inside the browser, it could reside inside the Node.js Cypress task code. It should be kept secret from the web application, and sending it into the browser seems dangerous. Keep it inside the config Node.js process.
Refactor for safety
Even in Cypress v15 we can use the following best practice for keeping secrets really secret:
1 | import { defineConfig } from 'cypress' |
Then the test can ask for the password value when needed, and never even ask for the "apiToken" value! The test can also "ask" for secrets using cy.task('getSecret') command, when needed. Here is the rewritten test.
1 | it('logs in', () => { |
Just don't name any secrets using CYPRESS_ prefix to avoid accidentally putting the value into Cypress.env object! We start Cypress with process environment variables set like this:
1 | USER_PASSWORD=secret! API_TOKEN=abc123$v101 cypress run |
💡 You can use my CLI utility as-a to inject multiple blocks of environment variables at once.
If an application tries to print Cypress.env() object, it sees just the "username", since everything else is "kept" inside the Node.js process env object and not bundled into the browser spec.

1 | if (window.Cypress) { |
Cypress v16 cy.env command
So here is the big change coming to Cypress v16: instead of writing custom task "getSecret", there is a new command cy.env for retrieving the secret on demand. Your Cypress config file splits all variables into "expose" (public) and "env" (secrets) blocks:
1 | import { defineConfig } from 'cypress' |
Do you need the username? You can get it immediately using the new Cypress.expose static method. You want to hear a secret? Call cy.env command.
1 | const username = Cypress.env('username') |
Any process env values that start with CYPRESS_ prefix are automatically set to the env block, so they are considered secrets. So you can overwrite the password specified in the Cypress config file:
1 | overwrite using CYPRESS_ env variable |
You can also overwrite the non-secret values using the new CLI option --expose (there is no process env setting to overwrite an exposed value)
1 | npx cypress run --expose [email protected] --env password=gbPass |
Migrating from Cypress.env for everything might take some time. I have a few plugins that I am migrating right now, but overall it is not too bad. Just think what you really need for your test, and if the values are secrets to be used in the browser vs secrets that should never go into the browser.
- For non-secret values, use the
exposeobject and theCypress.exposestatic method. - For secrets needed by the browser test, use the
cy.envcommand to get the value when needed - For "background" secrets that are not meant to be used inside the browser at all, use the
process.envobject and access from the Cypress config and thesetupNodeEventscallback
Stay safe out there!