How to Extend Cypress Commands: A Complete Guide to Custom Commands and Queries

You can extend Cypress commands by using Cypress.Commands.add() for new commands, Cypress.Commands.overwrite() for modifying existing ones, and Cypress.Commands.addQuery() for custom queries, all of which are managed by the driver in packages/driver/src/cypress/commands.ts.

The cypress-io/cypress repository exposes a global Cypress.Commands API that allows you to extend Cypress commands to encapsulate repetitive logic, enforce project-specific conventions, and blend custom utilities seamlessly into the Cypress command chain. Understanding how to properly add and overwrite commands requires knowledge of the internal validation system, command types, and the TypeScript implementation found in the driver package.

Understanding the Cypress.Commands API

The core implementation of how to extend Cypress commands lives in [packages/driver/src/cypress/commands.ts](https://github.com/cypress-io/cypress/blob/develop/packages/driver/src/cypress/commands.ts). When a test runs, the driver creates a Commands manager that handles registration, validation, and execution of all custom and built-in commands.

Internal Command Lifecycle

According to the source code in commands.ts, the driver implements several safeguards when you extend Cypress commands:

  • Reserved Name Protection: The system builds a reservedCommandNames set during initialization to prevent overwriting core Cypress APIs like cy.visit or cy.get directly through the add API.
  • Built-in Tracking: All built-in commands are recorded in builtInCommandNames and builtInCommands to prevent duplicate custom names and to enable the overwrite functionality.
  • Type Derivation: The command type (parent, child, or dual) is automatically derived from the options.prevSubject parameter.

Command Types and prevSubject

When you extend Cypress commands, the prevSubject option determines how the command interacts with the command chain:

  • Parent (prevSubject: false): Starts a new chain, receiving no subject from previous commands.
  • Child (prevSubject: 'element' or true): Requires a previous subject to operate on.
  • Dual (prevSubject: 'optional'): Can work with or without a previous subject.

Adding Custom Commands to Cypress

The Cypress.Commands.add(name, options?, fn) method is the primary way to extend Cypress commands. The implementation in commands.ts (lines 87-121) validates the name, determines the command type, and registers it via cy.addCommand.

Parent Commands (No Previous Subject)

Use parent commands to start a new chain. These are utilities that don't require an existing DOM element or subject.

// cypress/support/commands.ts
Cypress.Commands.add('login', (email: string, password: string) => {
  cy.request('POST', '/api/login', { email, password })
    .its('body')
    .then(body => {
      window.localStorage.setItem('authToken', body.token)
    })
})

Usage: cy.login('joe@example.com', 's3cr3t')

Child Commands (Receiving a Previous Subject)

Child commands receive the subject yielded by the previous command as their first argument. Specify { prevSubject: 'element' } in the options.

Cypress.Commands.add(
  'dragTo',
  { prevSubject: 'element' },
  (subject, targetSelector) => {
    // `subject` is the element yielded by cy.get()
    cy.wrap(subject).trigger('mousedown')
    cy.get(targetSelector).trigger('mousemove').trigger('mouseup')
  },
)

Usage: cy.get('.draggable').dragTo('#dropzone')

Dual Commands (Optional Subject)

Dual commands can operate whether or not they receive a previous subject, making them flexible for different chain contexts.

Cypress.Commands.add(
  'clickIfExists',
  { prevSubject: 'optional' },
  (subject, selector) => {
    if (subject) {
      cy.wrap(subject).click()
    } else {
      cy.get(selector).click()
    }
  },
)

Usage: cy.get('.maybe').clickIfExists('.fallback') or cy.clickIfExists('.standalone')

Creating Custom Queries in Cypress

Unlike commands, queries in Cypress are designed to return values rather than chainable Cypress instances. You can extend Cypress commands with queries using Cypress.Commands.addQuery(), implemented in packages/driver/src/cypress/commands.ts (lines 158-173).

Cypress.Commands.addQuery('getAll', () => {
  return () => Cypress.$('*')
})

// In a test
const allElements = cy.getAll()
expect(allElements).to.have.length.greaterThan(0)

Queries cannot clash with existing commands or reserved names, and they are validated separately from the command registry.

Overwriting Built-in Cypress Commands

When you need to modify existing behavior rather than create new commands, use Cypress.Commands.overwrite(name, fn). The source code (lines 123-156 in commands.ts) wraps the original implementation so your new function can invoke it via the originalFn parameter.

Cypress.Commands.overwrite('visit', (originalFn, url, options) => {
  // Prepend a base URL automatically
  const base = Cypress.env('BASE_URL')
  return originalFn(`${base}${url}`, options)
})

This approach preserves the original command's signature while allowing you to inject custom logic, such as automatic authentication, logging, or environment-specific URL handling.

Internal Validation and Error Handling

When you extend Cypress commands, the driver performs strict validation. The internalError function (lines 30-39 in commands.ts) throws detailed Cypress errors using messages defined in [error_messages.ts](https://github.com/cypress-io/cypress/blob/develop/packages/driver/src/cypress/error_messages.ts).

Key validation rules include:

  • Name Conflicts: You cannot add a custom command with the same name as a built-in command unless you use overwrite.
  • Reserved Names: Attempting to use names reserved for core Cypress APIs triggers an immediate error.
  • Query Separation: Query names must not conflict with command names or reserved names.

Summary

  • Extend Cypress commands using Cypress.Commands.add(name, options?, fn) for new functionality or Cypress.Commands.overwrite(name, fn) to modify existing commands.
  • Specify command types via prevSubject: use false for parent commands, 'element' for child commands, and 'optional' for dual commands.
  • Add custom queries with Cypress.Commands.addQuery() when you need to return values rather than chainable Cypress instances.
  • The driver validates all custom additions against reserved names and built-in commands in packages/driver/src/cypress/commands.ts, throwing detailed errors from error_messages.ts when conflicts occur.
  • Overwrite existing commands by calling originalFn within your custom implementation to preserve core functionality while adding custom logic.

Frequently Asked Questions

What is the difference between Cypress.Commands.add() and Cypress.Commands.addQuery()?

Cypress.Commands.add() creates a standard command that returns a chainable Cypress instance, suitable for actions like clicking, typing, or API requests. Cypress.Commands.addQuery() creates a query that returns a static value or DOM element directly without the Cypress chain wrapper, making it ideal for reading state or selecting elements that don't need additional command chaining.

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

Yes, you can overwrite built-in commands using Cypress.Commands.overwrite(name, fn), but you cannot overwrite them using add(). The overwrite method provides access to originalFn, allowing you to wrap the existing implementation. However, you cannot overwrite reserved names that are part of the core Cypress API surface beyond the standard commands.

How does Cypress prevent me from accidentally breaking core commands?

The driver maintains reservedCommandNames and builtInCommandNames sets during initialization in packages/driver/src/cypress/commands.ts. When you attempt to add a command with a conflicting name, the internalError function throws a descriptive error before registration completes, protecting core functionality from accidental overrides.

Where should I place my custom command definitions in a Cypress project?

Define custom commands in cypress/support/commands.ts (or .js). This file is typically imported in cypress/support/e2e.ts (or index.js in older projects) to ensure commands are available throughout your test suite. The TypeScript definitions for your custom commands should also be added to cypress/support/index.d.ts or included in your commands.ts file for IDE autocomplete support.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →