skip to content

Why does a TypeScript Cypress project add its own cypress/tsconfig.json?

level: middleimportance: nice to knowfreq 28%

answer

  1. Two type worlds in one repository
  2. Globals do not share a namespace
  3. Chai and jQuery ship as globals
  4. A second config file, not a second language
  5. Scope the compiler's declaration package list

basics

~10 s

To scope TypeScript's global type definitions to Cypress. A tsconfig.json inside the cypress folder with types set to cypress and node stops Chai, jQuery and another runner's globals from colliding with Cypress's own declarations.

solid answer

~40 s

Cypress ships official TypeScript declarations, but they land in the same global namespace as everything else the project types. The documented layout is a `tsconfig.json` inside the `cypress/` folder with `"types": ["cypress", "node"]` and `"include": ["**/*.ts"]`. The `types` array tells the compiler to load only those global declaration packages, which matters because Chai and jQuery — both bundled with Cypress — are global namespaces, so a second copy pulled in through `@types/chai` or `@types/jquery` can nest and conflict. The same file is the fix when another JavaScript test runner shares the repository: give that runner the root `tsconfig.json`, exclude the `cypress` folder from it, and its `describe`, `it` and `expect` types never meet Cypress's. TypeScript 5.x, 6.x or 7.x is required as of Cypress 16.

code

json · 9 lines
json
{
  "compilerOptions": {
    "target": "es6",
    "lib": ["es6", "dom"],
    "sourceMap": true,
    "types": ["cypress", "node"]
  },
  "include": ["**/*.ts"]
}

go deeper

for a junior

Know that a Cypress project written in TypeScript usually carries a small tsconfig.json inside the cypress folder, and that Cypress supplies its own type declarations.

for a middle

Explain the mechanism: Chai and jQuery are global namespaces, so a duplicate declaration package nests and conflicts unless the types array narrows what the compiler loads.

for a senior

Be ready to untangle a real repository where one config covers both Cypress and another runner, naming which globals collided and how you split the two compilations.

for a principal

Decide whether the suite shares the application's compiler settings or keeps its own, and weigh duplicated configuration against type drift between the app and its specs.

## What a `types` array actually controls TypeScript loads two kinds of type information: whatever your files explicitly import, and every **global** declaration package it can find. The second set is where the trouble starts. Left alone, the compiler sweeps up every package under `node_modules/@types`, and those packages declare names in the global scope — `describe`, `it`, `expect`, `$`, and assertion interfaces. Cypress bundles Chai and jQuery and declares its own versions of those globals. If a second copy arrives through `@types/chai` or `@types/jquery`, the package manager can nest both, and the compiler ends up looking at two incompatible declarations of one global name. `"types": ["cypress", "node"]` switches that automatic sweep off and names exactly which declaration packages this folder may load. It installs nothing, and it does not restrict what an individual file imports — it only bounds the **global** set. ## The recommended layout The documented shape is a small `tsconfig.json` inside the `cypress/` folder: - `"types": ["cypress", "node"]` — Cypress's own declarations plus Node's, the latter for support files and anything touching the Node side of the project. - `"include": ["**/*.ts"]` — the specs and support files in that folder and nothing above it. - `"target"` and `"lib"` set for a browser context, since the compiled spec executes in one. That is the whole file for most projects. It exists because the Cypress folder has different global types from the rest of the repository, not because Cypress needs a build step of its own — Cypress compiles JavaScript and TypeScript out of the box. ## Splitting Cypress from another test runner The clearest case for a second config is a repository that runs Cypress beside another JavaScript test runner. Both declare `describe` and `it`; Cypress's bundled Chai and the other runner's matcher library both declare `expect`. Merged into one compilation, they conflict. The documented fix is two configurations, not one clever one: 1. Create `cypress/tsconfig.json` with the narrow `types` array above. 2. Add `"exclude": ["cypress", "cypress.config.ts", "node_modules"]` to the root `tsconfig.json` so the other runner's compilation never sees Cypress's globals. | Compilation | Config file | Globals it loads | |---|---|---| | Cypress specs and support files | `cypress/tsconfig.json` | Cypress, Chai, jQuery, Node | | Everything else, including the other runner | root `tsconfig.json` | whatever that runner declares | The same pattern applies whenever the application's compiler settings and the test suite's settings genuinely differ — strictness, module resolution, target — rather than only for a global clash. ## The `include` glob trap One failure mode catches teams repeatedly: a declaration file that the compiler never reads. If you move your custom-command types into a standalone `.d.ts` file, verify that the `include` globs of **every** config that needs to see it actually match. A `global.d.ts` placed under `cypress/support/` is not reliably picked up by `"include": ["**/*.ts"]` in every setup. The safe forms are `["**/*.ts", "**/*.d.ts"]`, a bare `["**/*"]`, or listing the file by path. A missed declaration file produces no error of its own — the custom command simply shows as unknown, which reads like a broken type declaration rather than a missing include. ## Version and placement details that trip people up - **TypeScript 5.x, 6.x or 7.x** is required as of Cypress 16; older compilers are no longer supported, and the requirement rose across the 13, 15 and 16 lines. - The file lives in `cypress/`, not at the repository root. A root-level `tsconfig.json` still governs the application; the two coexist and the nearer one wins for files beneath it. - Cypress config files are a separate matter. `cypress.config.ts`, `.mts` and `.cts` are transpiled by tsx, and how a config loads as ESM or CommonJS follows the file extension and the nearest `package.json` `"type"`, not `compilerOptions.module`. - Restarting the editor's TypeScript server is a real step, not folklore. A newly added or moved config is frequently not picked up until the language server reloads. ## What the file does not do - It does not change how Cypress runs a spec. Types are erased; the browser receives JavaScript. - It does not select a preprocessor. That is chosen on the `file:preprocessor` node event. - It does not substitute for declaring custom commands on the `Chainable` interface; scoping the global set and augmenting it are separate jobs. - It does not require a community types package for Cypress. Cypress publishes its own declarations, so a `@types` package for it is not something you install. Understood this way, `cypress/tsconfig.json` is not TypeScript ceremony. It is the small amount of configuration that keeps one language with one set of globals from colliding with itself when two different test tools share a repository.

  • What breaks if the cypress/tsconfig.json include glob misses a .d.ts file?
    The declarations are simply never loaded, so a custom command, or a property you added to Cypress's `ApplicationWindow` type, shows as unknown. `"include": ["**/*.ts"]` does not reliably match `.d.ts` files in every setup. The safe forms are `["**/*.ts", "**/*.d.ts"]`, a bare `["**/*"]`, or listing the declaration file by path. Nothing errors on its own — the symptom looks like a broken declaration rather than a missing include.
  • Does adding TypeScript change how Cypress runs the spec?
    No. Types are erased at compile time and the browser receives JavaScript either way. TypeScript changes *when* you learn about a mistake — in the editor rather than in a failing run — and gives editor completion for every `cy.*` command. It does not add a language to the runtime, and it does not change the command queue, retry-ability or timing.

saying these in an interview costs you the question

  • Thinks the types array installs npm packages
  • Says TypeScript needs a separate Cypress preprocessor
  • Puts Cypress and another runner under one tsconfig
  • Believes type declarations survive into the running spec
  • Assumes a community @types package for Cypress is needed