# How to Integrate Custom Commands in Cypress: A Complete Guide with Code Examples

> Learn how to integrate custom commands in Cypress using Cypress.Commands.add() and Cypress.Commands.overwrite() with code examples. Enhance your testing workflow today.

- Repository: [Cypress.io/cypress](https://github.com/cypress-io/cypress)
- Tags: how-to-guide
- Published: 2026-08-06

---

**Use `Cypress.Commands.add()` to extend the `cy` object with new commands, or `Cypress.Commands.overwrite()` to modify existing ones—all from your [`cypress/support/commands.ts`](https://github.com/cypress-io/cypress/blob/main/cypress/support/commands.ts) file.**

Integrating custom commands into your Cypress test suite lets you encapsulate repetitive workflows, extend the `cy` API, and keep tests readable. According to the cypress-io/cypress source code, the browser-side driver handles all registration through the `Cypress.Commands` object defined in [`packages/driver/src/cypress/commands.ts`](https://github.com/cypress-io/cypress/blob/main/packages/driver/src/cypress/commands.ts). This guide walks through the internals, command types, and practical implementations.

## Where Custom Commands Live in Cypress Internals

Cypress runs across three runtimes: the Node CLI, the Electron main process, and the browser-side driver. The **custom command API lives exclusively in the driver** (`@packages/driver`), ensuring your functions execute in the same sandbox as the application under test.

When the test runner initializes, the `Commands` factory in [`packages/driver/src/cypress/commands.ts`](https://github.com/cypress-io/cypress/blob/main/packages/driver/src/cypress/commands.ts) builds two internal registries:

- **`commands`** — stores definitions for chainable custom commands
- **`queries`** — stores value-returning helpers (like `cy.get`, `cy.find`)

Calls from test files serialize to the driver, which executes your functions and handles retries, logging, and error bubbling exactly like native commands.

## Adding Custom Commands with Cypress.Commands.add()

The `Cypress.Commands.add(name, [options], fn)` method is the primary entry point. The driver validates the name against built-ins and reserved properties, then derives the **command type** from the optional `prevSubject` flag.

### Simple Parent Commands

Parent commands start a new command chain without requiring a previous subject.

```javascript
// cypress/support/commands.ts
Cypress.Commands.add('login', (username: string, password: string) => {
  cy.visit('/login')
  cy.get('#user').type(username)
  cy.get('#pass').type(password)
  cy.get('form').submit()
})

```

Usage in tests:

```javascript
cy.login('alice', 'password123')
cy.url().should('include', '/dashboard')

```

### Child Commands with prevSubject

Child commands operate on a subject yielded by a previous command. Set `prevSubject: 'element'` (or `'document'`, `'window'`) in options.

```javascript
Cypress.Commands.add(
  'selectOption',
  { prevSubject: 'element' },
  (subject: JQuery<HTMLElement>, option: string) => {
    cy.wrap(subject).select(option)
  }
)

```

Usage:

```javascript
cy.get('select#colors').selectOption('red')

```

### Dual-Type Commands with Optional Subjects

Use `prevSubject: 'optional'` to create commands that work both ways—independently or chained.

```javascript
Cypress.Commands.add(
  'logMessage',
  { prevSubject: 'optional' },
  (subject: JQuery<HTMLElement> | void, msg: string) => {
    if (subject) {
      cy.wrap(subject).then(() => console.log(msg))
    } else {
      console.log(msg)
    }
  }
)

```

Both patterns work:

```javascript
cy.logMessage('Standalone message')
cy.get('#some-element').logMessage('Message after element')

```

## Overwriting Existing Cypress Commands

Modify built-in behavior with `Cypress.Commands.overwrite(name, fn)`. The original implementation is passed as the first argument to your function.

```javascript
Cypress.Commands.overwrite(
  'click',
  (originalFn, subject: JQuery<HTMLElement>, options?: Partial<Cypress.ClickOptions>) => {
    cy.log('About to click an element')
    return originalFn(subject, options).then(() => {
      cy.log('Click completed')
    })
  }
)

```

The wrapper pattern preserves native retry logic and logging while letting you inject pre/post processing.

## Adding Query Helpers with addQuery()

Queries return values rather than chainables. They're registered via `addQuery` in the same [`commands.ts`](https://github.com/cypress-io/cypress/blob/main/commands.ts) file.

```javascript
Cypress.Commands.addQuery('sessionStorageItem', (key: string) => {
  return () => {
    return window.sessionStorage.getItem(key)
  }
})

```

Usage:

```javascript
cy.sessionStorageItem('token').should('eq', 'abc123')

```

Unlike commands, queries don't return a Cypress chainable—they yield the value directly for assertions.

## Command Type Resolution and Validation

The driver uses **`getTypeByPrevSubject`** (in [`packages/driver/src/cypress/commands.ts`](https://github.com/cypress-io/cypress/blob/main/packages/driver/src/cypress/commands.ts)) to determine command type:

| `prevSubject` value | Resulting type | Behavior |
|---------------------|----------------|----------|
| `undefined` (default) | **parent** | Starts new chain |
| `'element'` / `'document'` / `'window'` | **child** | Requires matching subject |
| `'optional'` | **dual** | Works either way |
| `false` | **parent** | Explicitly no subject |

Name conflicts trigger **`internalError`** if you attempt to shadow built-ins like `get`, `visit`, or `log`. The **`PLACEHOLDER_COMMANDS`** set (containing `.mount`, `.hover`, etc.) allows special exceptions for commands added without triggering duplicate errors.

## Complete Working Example

Here's a production-ready [`cypress/support/commands.ts`](https://github.com/cypress-io/cypress/blob/main/cypress/support/commands.ts) combining all patterns:

```typescript
// cypress/support/commands.ts

// Extend Cypress namespace for TypeScript
declare global {
  namespace Cypress {
    interface Chainable {
      login(username: string, password: string): Chainable<void>
      selectOption(value: string): Chainable<Element>
      logMessage(message: string): Chainable<Element | void>
      sessionStorageItem(key: string): Chainable<string | null>
    }
  }
}

// Parent command
Cypress.Commands.add('login', (username, password) => {
  cy.visit('/login')
  cy.get('[data-testid=username]').type(username)
  cy.get('[data-testid=password]').type(password)
  cy.get('[data-testid=submit]').click()
})

// Child command
Cypress.Commands.add(
  'selectOption',
  { prevSubject: 'element' },
  (subject, value) => {
    cy.wrap(subject).select(value)
    return cy.wrap(subject)
  }
)

// Dual command
Cypress.Commands.add(
  'logMessage',
  { prevSubject: 'optional' },
  (subject, message) => {
    const timestamp = new Date().toISOString()
    if (subject) {
      cy.wrap(subject).then($el => {
        console.log(`[${timestamp}] ${message}:`, $el)
      })
      return cy.wrap(subject)
    }
    console.log(`[${timestamp}] ${message}`)
  }
)

// Query helper
Cypress.Commands.addQuery('sessionStorageItem', (key) => {
  const val = window.sessionStorage.getItem(key)
  return () => val
})

```

## Key Source Files for Reference

| File | Purpose |
|------|---------|
| [`packages/driver/src/cypress/commands.ts`](https://github.com/cypress-io/cypress/blob/main/packages/driver/src/cypress/commands.ts) | Core implementation of `add`, `overwrite`, `addQuery`, `overwriteQuery`; contains `getTypeByPrevSubject`, `internalError`, `PLACEHOLDER_COMMANDS` |
| [`cli/types/cypress.d.ts`](https://github.com/cypress-io/cypress/blob/main/cli/types/cypress.d.ts) | TypeScript definitions including command API overloads |
| [`packages/driver/src/cypress/command_queue.ts`](https://github.com/cypress-io/cypress/blob/main/packages/driver/src/cypress/command_queue.ts) | Underlying queue mechanics for command execution and retries |

## Summary

- **`Cypress.Commands.add()`** registers new commands in the driver-side registry, with type derived from `prevSubject`
- **`prevSubject: 'element'`** creates child commands; **`'optional'`** creates dual commands; omitting it creates parent commands
- **`Cypress.Commands.overwrite()`** wraps original implementations so you can augment or replace behavior
- **`addQuery()`** registers value-returning helpers distinct from chainable commands
- All registration flows through [`packages/driver/src/cypress/commands.ts`](https://github.com/cypress-io/cypress/blob/main/packages/driver/src/cypress/commands.ts), which validates names against built-ins and reserved properties

## Frequently Asked Questions

### How do I add TypeScript types for custom commands?

Extend the `Cypress.Chainable` interface in a declaration merged namespace. Place this in [`cypress/support/commands.ts`](https://github.com/cypress-io/cypress/blob/main/cypress/support/commands.ts) or a dedicated [`cypress/support/index.d.ts`](https://github.com/cypress-io/cypress/blob/main/cypress/support/index.d.ts). The definitions in [`cli/types/cypress.d.ts`](https://github.com/cypress-io/cypress/blob/main/cli/types/cypress.d.ts) show how Cypress structures these overloads for built-ins.

### Can I overwrite built-in commands like cy.get or cy.visit?

Technically yes, but the driver throws via `internalError` for reserved names. Only commands in `PLACEHOLDER_COMMANDS` (like `.mount`, `.hover`) can be added without conflict. For others, use `overwrite`, which the driver permits for extending behavior while preserving the original reference.

### What's the difference between a command and a query?

Commands return a chainable and participate in Cypress's retry- and log-system. Queries (added via `addQuery`) return a value directly and are stored in the separate `queries` registry. Use queries for simple value extraction; commands for operations that need chaining and automatic retries.

### Where should I register custom commands?

Define them in [`cypress/support/commands.ts`](https://github.com/cypress-io/cypress/blob/main/cypress/support/commands.ts) (or `.js`), which the scaffolded [`cypress.config.js`](https://github.com/cypress-io/cypress/blob/main/cypress.config.js) imports by default. This file runs before specs, making additions available globally via `cy`.