---
title: "Extending the Cypress Config File"
url: "https://www.cypress.io/blog/extending-the-cypress-config-file"
description: "In this blog post I will show how to implement extends syntax in the Cypress JSON configuration file without waiting for the Cypress team to add support."
---

# Extending the Cypress Config File

June 18, 2020

By Gleb Bahmutov

Many tools allow one config file to extend another one. For example, [ESLint](https://eslint.org/docs/user-guide/configuring?ref=cypress-io.ghost.io) allows one to apply recommended settings and then add several more rules:

```json
{
    "plugins": [
        "react"
    ],
    "extends": [
        "eslint:recommended",
        "plugin:react/recommended"
    ],
    "rules": {
       "react/no-set-state": "off"
    }
}
```

Similarly, the [TypeScript compiler](https://www.typescriptlang.org/docs/handbook/tsconfig-json.html?ref=cypress-io.ghost.io) allows you to extend the configuration from the [base file](https://www.typescriptlang.org/tsconfig?ref=cypress-io.ghost.io#extends) using the `extends` keyword. If we place most of the settings in `configs/base.json`

```json
{
  "compilerOptions": {
    "noImplicitAny": true,
    "strictNullChecks": true
  }
}
```

Then we can write `tsconfig.json` extending the base config:

```json
{
  "extends": "./configs/base",
  "files": ["main.ts", "supplemental.ts"]
}
```

Cypress does not support `extends` syntax in its [configuration file](https://on.cypress.io/configuration?ref=cypress-io.ghost.io). If you want to apply different settings, you need to write a complete second configuration file and use it via the `--config-file <filename>` command line argument.

For example, when working with tests locally, we might use the default `cypress.json` file:

```json
{
  "baseUrl": "http://localhost:8080",
  "defaultCommandTimeout": 2000,
  "video": true
}
```

When running tests against staging server, we might use a few different settings placed in `staging.json`

```json
{
  "baseUrl": "http://localhost:8080",
  "defaultCommandTimeout": 5000,
  "video": false,
  "env": {
    "staging": true
  }
}
```

Then the continuous integration server would use the command `npx cypress run --config-file staging.json` to use the later configuration file.

**Note:** make sure the top-level configuration values like `baseUrl`, `video`, and others are placed at the top-level of the configuration files and do not accidentally end up in the `env` object. They won't work correctly inside the `env` object!

Hmm, seems like most settings would be duplicated. We could still use the `cypress.json` file and override the config values and the environment variables via command line arguments:

```
npx cypress run --config defaultCommandTimeout=5000 --env staging=true
```

I do not like the above approach, because it hides the intent and spreads the related settings across multiple files. In this blog post I will show how to implement `extends` syntax in the Cypress JSON configuration file without waiting for the Cypress team to add support.

## Plugins file

When Cypress loads your project, you can change any configuration or environment value programmatically from the [plugins file](https://on.cypress.io/configuration?ref=cypress-io.ghost.io#Plugins). For example, in the [](https://github.com/bahmutov/config-extends-example?ref=cypress-io.ghost.io)[bahmutov/config-extends-example](https://github.com/bahmutov/config-extends-example?ref=cypress-io.ghost.io) repository we first print the current configuration from `cypress/plugins/index.js`

```javascript
/**
 * @type {Cypress.PluginConfig}
 */
module.exports = (on, config) => {
  // `on` is used to hook into various events Cypress emits
  // `config` is the resolved Cypress config
  console.log(config)
  return config
}
```

The list of settings is long ...

```
{
  animationDistanceThreshold: 5,
  fileServerFolder: '/Users/gleb/git/config-extends-example',
  baseUrl: null,
  fixturesFolder: '/Users/gleb/git/config-extends-example/cypress/fixtures',
  blacklistHosts: null,
  chromeWebSecurity: true,
  modifyObstructiveCode: true,
  ...
  experimentalGetCookiesSameSite: false,
  experimentalSourceRewriting: false,
  experimentalComponentTesting: false,
  projectRoot: '/Users/gleb/git/config-extends-example',
  configFile: '/Users/gleb/git/config-extends-example/cypress.json'
}
```

The very last setting `configFile` is what we are after. It was added in [Cypress v4.1.0](https://on.cypress.io/changelog?ref=cypress-io.ghost.io#4-1-0) to allow the plugins file to "know" what configuration file has been loaded. Let's add a second configuration file extending the `cypress.json`.

```
// cypress.json
{
  "defaultCommandTimeout": 2000,
  "video": true
}
// staging.json
{
  "extends": "./cypress.json",
  "video": false
}
```

Let's run Cypress using the `staging.json` configuration file.

```
npx cypress run --config-file staging.json
...
long list of resolved config values
```

Notice the list of configuration values does NOT include the `extends` property, because Cypress whitelists config keys. Here is where the plugins code can help. Because it "knows" which configuration file has been loaded, it can load it again as JSON, find the `extends` property and load the base config file, then merge the two configs:

```js
const deepmerge = require('deepmerge')
const path = require('path')
module.exports = (on, config) => {
  // `on` is used to hook into various events Cypress emits
  // `config` is the resolved Cypress config
  console.log(config)

  const configJson = require(config.configFile)
  if (configJson.extends) {
    const baseConfigFilename = path.join(config.projectRoot, configJson.extends)
    const baseConfig = require(baseConfigFilename)
    console.log('merging %s with %s', baseConfigFilename, config.configFile)
    return deepmerge(baseConfig, configJson)
  }

  return config
}
```

Notice that we return merged base config plus config JSON (not the `config` argument passed into the plugins file). Cypress will automatically re-merge the resolved config with the returned result. Thus we only need to "worry" about our settings and not the full config.

We can see the final configuration values by running Cypress in the interactive mode

```
npx cypress open --config-file staging.json
```

Open the `Settings / Configuration` tab and notice that the `defaultCommandTimeout: 2000` comes from `plugins` - this is the result of the merge returned from the `cypress.json` base file. The value `video: false` came from the `config` which in our case was `staging.json` configuration file.

We can load and merge configs recursively to allow severals `extends` levels.

```js
const deepmerge = require('deepmerge')
const path = require('path')

function loadConfig(filename) {
  const configJson = require(filename)
  if (configJson.extends) {
    const baseConfigFilename = path.join(
      path.dirname(filename), configJson.extends)
    const baseConfig = loadConfig(baseConfigFilename)
    console.log('merging %s with %s', baseConfigFilename, filename)
    return deepmerge(baseConfig, configJson)
  } else {
    return configJson
  }
}

module.exports = (on, config) => {
  // `on` is used to hook into various events Cypress emits
  // `config` is the resolved Cypress config
  return loadConfig(config.configFile)
}
```

I have placed the recursive config load into [](https://github.com/bahmutov/cypress-extends?ref=cypress-io.ghost.io)[bahmutov/cypress-extends](https://github.com/bahmutov/cypress-extends?ref=cypress-io.ghost.io) and published it as the `@bahmutov/cypress-extends` NPM package. Give it a try:

```
npm i -D @bahmutov/cypress-extends
# or
yarn add -D @bahmutov/cypress-extends
```

Then use it from the plugins file

```js
module.exports = (on, config) => {
  // `on` is used to hook into various events Cypress emits
  // `config` is the resolved Cypress config
  return require('@bahmutov/cypress-extends')(config.configFile)
}
```

**Tip:** add a schema URL to your Cypress configuration file to get [code completion](https://on.cypress.io/IDE-integration?ref=cypress-io.ghost.io) for config settings

```json
{
  "$schema": "https://on.cypress.io/cypress.schema.json"
}
```