env
Securely read Cypress environment variables from within your tests. cy.env() reads the values set with env in your Cypress configuration, including those from cypress.env.json, CYPRESS_* environment variables, the --env CLI flag, and setupNodeEvents.
Reach for cy.env() when a test needs a sensitive value, such as an API key, a
password, or a token. cy.env() reads the values from the Node process when a
test asks for them and hands over only the keys you name. That gives you:
- Only the keys you ask for. Your test code receives the variables you name and nothing else, so a test that needs one secret doesn't carry all of them.
- Key names in the Command Log, never values. You can see which variables a test read without the values showing up in the run.
- Easier audits. Every secret your tests read goes through a
cy.env()call, so a search forcy.env(shows you everywhere secrets are read. - Access across origins. It works inside
cy.origin()callbacks, so a login flow on another domain can read its credentials there.
For a public value you want to read synchronously, such as a feature flag or an
environment label, use Cypress.expose() instead. See
When to use cy.env() vs Cypress.expose().
Read-only command
cy.env() reads environment variables and cannot set or change them. To set them, use one of the methods in the Environment Variables & Secrets guide.
cy.env() logs the key names you ask for and never the values. That protection
ends at the command boundary. The object cy.env() yields is an ordinary
JavaScript object, and Cypress does not mask, redact, or track the values inside
it. Assertions, .its(),
.invoke(), and any chained command that fails can all
print a value to the Command Log and
the console output. What happens
to a value after cy.env() yields it is up to you. See
Handling the yielded value safely.
Syntax
cy.env(keys)
cy.env(keys, options)
Usage
Correct Usage
- cypress.config.js
- cypress.config.ts
const { defineConfig } = require('cypress')
module.exports = defineConfig({
env: {
apiUrl: 'https://api.example.com',
apiKey: 'secret-key-12345',
},
expose: {
environment: 'staging', // Public configuration value
},
})
import { defineConfig } from 'cypress'
export default defineConfig({
env: {
apiUrl: 'https://api.example.com',
apiKey: 'secret-key-12345',
},
expose: {
environment: 'staging', // Public configuration value
},
})
// Get a single environment variable
cy.env(['apiUrl']).then(({ apiUrl }) => {
cy.request(`${apiUrl}/users`).its('status').should('eq', 200)
})
// Get multiple environment variables
cy.env(['apiUrl', 'apiKey']).then(({ apiUrl, apiKey }) => {
cy.request({
url: `${apiUrl}/users`,
headers: { Authorization: `Bearer ${apiKey}` },
})
.its('status')
.should('eq', 200)
})
// With options
cy.env(['apiUrl'], { log: false }).then(({ apiUrl }) => {
// Use apiUrl
})
Arguments
keys (String[])
A non-empty array of environment variable keys to retrieve from Cypress. Each key must be a non-empty string.
If a variable is not defined, reading it from the yielded object returns undefined.
options (Object)
Pass an options object to change the default behavior of cy.env().
| Option | Default | Description |
|---|---|---|
log | true | Displays the command in the Command log. Only variable names are logged, never values. |
timeout | 4000 | Time to wait for cy.env() to resolve before timing out. This default is fixed and does not follow defaultCommandTimeout. |
Yields
cy.env() yields an object with the values found for the environment variable keys requested.
cy.env(['apiUrl', 'apiKey']).then((env) => {
// env = { apiUrl: 'https://api.example.com', apiKey: 'secret-key-12345' }
})
Examples
Read a single variable
Get a single environment variable and chain other commands inside the
.then() callback:
cy.env(['appUrl']).then(({ appUrl }) => {
cy.visit(appUrl)
cy.get('h1').should('be.visible')
})
Read multiple variables
Get multiple environment variables at once:
cy.env(['apiUrl', 'apiKey', 'timeout']).then(({ apiUrl, apiKey, timeout }) => {
cy.request({
url: `${apiUrl}/users`,
headers: { Authorization: `Bearer ${apiKey}` },
timeout: timeout || 5000,
})
})
Fall back to a default value
Reading a key that isn't set returns undefined, so a destructuring default
applies:
cy.env(['apiUrl']).then(({ apiUrl = 'http://localhost:3000' }) => {
cy.request(`${apiUrl}/health`).its('status').should('eq', 200)
})
Read a nested object value
cy.env() yields values with the type they were defined with, so an object
passed as JSON arrives as an object:
cypress run --env credentials='{"user":"jane","token":"abc123"}'
cy.env(['credentials']).then(({ credentials }) => {
cy.request({
url: '/api/me',
headers: { Authorization: `Bearer ${credentials.token}` },
log: false,
})
})
Use environment variables across tests
Store environment variables for use across multiple tests by using a before() hook:
describe('API tests', () => {
let apiBaseUrl
before(() => {
cy.env(['apiBaseUrl']).then(({ apiBaseUrl: url }) => {
apiBaseUrl = url
})
})
it('can make requests', () => {
cy.request(`${apiBaseUrl}/users`).its('status').should('eq', 200)
})
})
Fail fast when a required variable is missing
Check required variables once in a before() hook in your support file. A
missing value then fails with a clear message, rather than as an unexpected
401 in a later test:
before(() => {
cy.env(['apiKey']).then(({ apiKey }) => {
if (!apiKey) {
throw new Error(
'apiKey is not set. Add it to cypress.env.json or set CYPRESS_apiKey.'
)
}
})
})
In CI, set CYPRESS_apiKey from your CI provider's secret store. See
Set environment variables
for every supported method.
Use cy.env() in custom commands
Define a custom command in cypress/support/commands.ts (or .js) that reads
cy.env() for you:
Cypress.Commands.add('apiRequest', (endpoint, options = {}) => {
cy.env(['apiUrl', 'apiKey']).then(({ apiUrl, apiKey }) => {
cy.request({
...options,
url: `${apiUrl}${endpoint}`,
headers: {
Authorization: `Bearer ${apiKey}`,
...options.headers,
},
})
})
})
export {}
declare global {
namespace Cypress {
interface Chainable {
apiRequest(
endpoint: string,
options?: Partial<Cypress.RequestOptions>
): Chainable<Cypress.Response<any>>
}
}
}
Cypress.Commands.add('apiRequest', (endpoint, options = {}) => {
cy.env(['apiUrl', 'apiKey']).then(({ apiUrl, apiKey }) => {
cy.request({
...options,
url: `${apiUrl}${endpoint}`,
headers: {
Authorization: `Bearer ${apiKey}`,
...options.headers,
},
})
})
})
export {}
The export {} line makes the file a module, which TypeScript requires for
declare global. See
declare global requires a TypeScript module file.
Then call it from a test:
cy.apiRequest('/users').its('status').should('eq', 200)
Log in with cy.session()
Read credentials inside a cy.session() setup
function so the login runs once and is cached for later tests in the same spec
(set cacheAcrossSpecs to share it across
specs). Pass { log: false } when typing the password so it stays out of the
Command Log:
Cypress.Commands.add('login', () => {
cy.session('admin', () => {
cy.env(['adminUser', 'adminPassword']).then(
({ adminUser, adminPassword }) => {
cy.visit('/login')
cy.get('[name="username"]').type(adminUser)
cy.get('[name="password"]').type(adminPassword, { log: false })
cy.get('form').submit()
}
)
})
})
Read a variable inside cy.origin()
Variables from the surrounding test reach a cy.origin()
callback only through its args option. Call cy.env() inside the callback to
read a secret there without passing it in:
cy.origin('https://auth.example.com', () => {
cy.env(['ssoPassword']).then(({ ssoPassword }) => {
cy.get('#password').type(ssoPassword, { log: false })
})
})
Suppress command logging
Hide the cy.env() entry from the Command Log. Because cy.env() logs key
names and never values, use this option when you don't want the key names
visible in the Command Log:
cy.env(['acmeMigrationKey'], { log: false }).then(({ acmeMigrationKey }) => {
// The command and the key name don't appear in the Command Log
})
Handling the yielded value safely
cy.env() logs the key names you ask for and never the values. That guarantee
stops the moment the command yields. The object you receive is an ordinary
JavaScript object, and Cypress does not mask, redact, or track the values
inside it.
Keep the value inside a .then() callback and pass it straight to the command
that needs it. .then() and
.spread() add no entry to the Command Log, so the
value stays out of it.
Assert on a derived value, not on the secret
Every assertion writes to the Command Log, and the entry contains the values being compared. Assertions accept no logging options, so you cannot suppress them. Assert on a boolean you derive from the value instead.
Incorrect Usage
cy.env(['apiKey']).should('deep.include', { apiKey: 'secret-key-12345' })
// ❌ Command Log: assert expected { apiKey: 'secret-key-12345' } to deep
// include { apiKey: 'secret-key-12345' }
Correct Usage
cy.env(['apiKey']).then(({ apiKey }) => {
expect(Boolean(apiKey)).to.be.true
})
// ✅ Command Log: assert expected true to be true
Calling expect() inside a .then() callback still creates an assertion entry,
so a .then() callback alone is not enough. What you assert on is what matters.
Avoid .its() and .invoke() on the yielded object
.its() and .invoke() both add
the subject they were applied to and the value they yield to the console
output, so cy.env(['apiKey']).its('apiKey') prints the environment variable
value twice. Read the property inside a .then() callback instead.
Incorrect Usage
// ❌ Prints the environment variable value to the console output twice
cy.env(['apiKey']).its('apiKey')
Correct Usage
cy.env(['apiKey']).then(({ apiKey }) => {
// ✅ Use apiKey here
})
Keep the value out of downstream command logs
Commands that accept a log option, such as
cy.request() and .type(),
leave their entry out of the Command Log when you pass { log: false }. Use it
on any command you hand the value to:
cy.env(['apiKey']).then(({ apiKey }) => {
cy.request({
url: 'https://api.example.com/users',
headers: { Authorization: `Bearer ${apiKey}` },
log: false,
})
cy.get('[data-testid="token-field"]').type(apiKey, { log: false })
})
{ log: false } hides the entry from the Command Log. It does not redact the
value, and it has no effect on assertions.
When to use cy.env() vs Cypress.expose()
Both cy.env() and Cypress.expose() provide access to configuration values in Cypress, but they serve different security and execution needs. Choosing the right API helps avoid accidental exposure of sensitive data and keeps configuration intent clear.
Use Cypress.expose() for public configuration
Recommended when:
- Values are public or non-sensitive - Examples include feature flags, API versions, environment labels, or plugin configuration that is safe to appear in browser state.
- Synchronous access is needed -
Cypress.expose()returns values immediately, without requiring Cypress command chaining.
Use cy.env() for sensitive or secret values
Choose cy.env() when security, scoping, and controlled access matter. Recommended when:
- Values are sensitive - API keys, passwords, tokens, or any data that should not be broadly exposed to the browser.
- Security is a priority -
cy.env()only exposes the variables you explicitly request and does not automatically serialize them into browser state. - You're already working within Cypress command chains:
cy.env()is asynchronous and designed to be used inside Cypress tests and hooks.
Example: choosing the right API
// ✅ Use cy.env() for sensitive values
cy.env(['apiKey']).then(({ apiKey }) => {
cy.request({
url: 'https://api.example.com/users',
headers: { Authorization: `Bearer ${apiKey}` },
})
})
// ✅ Use Cypress.expose() for public configuration
const apiVersion = Cypress.expose('apiVersion') // Synchronous, public value
const featureFlag = Cypress.expose('featureFlag') // Safe to expose in browser
Migrate from Cypress.env()
cy.env() replaces Cypress.env(), which was deprecated in Cypress 15.10.0 and removed in Cypress 16.0. Cypress.env() hydrated every environment variable into the browser, while cy.env() returns only the keys you request.
On Cypress ^15.10.0, after migrating all usages, you can prevent future use of Cypress.env() by setting allowCypressEnv: false in your Cypress configuration. Cypress 16.0 removes both Cypress.env() and the allowCypressEnv option.
Cypress ^15.10.0 only:
- cypress.config.js
- cypress.config.ts
const { defineConfig } = require('cypress')
module.exports = defineConfig({
allowCypressEnv: false,
})
import { defineConfig } from 'cypress'
export default defineConfig({
allowCypressEnv: false,
})
See the Migration Guide for detailed migration instructions.
Notes
Test configuration overrides
Environment variables cannot be set using test configuration. On Cypress ^15.10.0, this was enforced when allowCypressEnv was set to false. In Cypress 16.0, passing env in a suite or test configuration override throws an error. If you need a per-test value readable in the browser, use expose instead.
Values are not limited to strings
cy.env() yields values exactly as they are defined in your configuration. A value set as a number, boolean, or object in your configuration file, cypress.env.json, or a JSON value passed to --env is yielded as that type.
Case sensitivity
Variable names are case-sensitive and must match exactly how they are defined in your configuration:
// Configuration
{
env: {
apiUrl: 'https://api.example.com',
}
}
// In test
cy.env(['apiUrl']) // ✅ Gets 'https://api.example.com'
cy.env(['APIURL']) // ❌ Returns undefined
Rules
Requirements
cy.env()requires being chained off ofcy.cy.env()requires a non-empty array of non-empty strings.
Assertions
cy.env()runs assertions you have chained only once and does not retry.
Timeouts
cy.env()can time out waiting for the environment variables to be read from the Node process.
Command Log
Read two environment variables
cy.env(['apiUrl', 'apiKey'])
The command above displays in the Command Log as:

When clicking on the env command within the Command Log, the console outputs
the requested key names under Env vars, never their values:

History
| Version | Changes |
|---|---|
| 15.10.0 | cy.env() command added |