Skip to main content
Cypress App

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 for cy.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().

info

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.

caution

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

const { defineConfig } = require('cypress')

module.exports = 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().

OptionDefaultDescription
logtrueDisplays the command in the Command log. Only variable names are logged, never values.
timeout4000Time 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:

cypress/support/e2e.js
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/support/commands.js
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:

const { defineConfig } = require('cypress')

module.exports = 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 of cy.
  • 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:

Command Log row for env showing the key names apiUrl and apiKey without their values

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

Console output for the env command listing Env vars as apiUrl and apiKey

History​

VersionChanges
15.10.0cy.env() command added

See also​