Cypress Custom Commands: How to Create, Extend, and Overwrite
Cypress custom commands are user-defined functions that extend the cy namespace, allowing you to encapsulate reusable test logic by registering them via Cypress.Commands.add() in your support files.
Cypress custom commands let you abstract repetitive test operations into reusable functions that behave like native Cypress APIs. By registering commands in cypress/support/commands.ts, you can create domain-specific testing languages that keep your specs clean and maintainable. The command registration engine lives in the core driver at packages/driver/src/cypress/commands.ts, exposing methods to add, overwrite, and query custom behavior.
What Are Cypress Custom Commands?
Cypress custom commands are functions that become part of the cy object, callable just like built-in methods such as cy.get() or cy.click(). According to the Cypress source code in packages/driver/src/cypress/commands.ts, the framework maintains a registry of built-in commands, reserved names, and placeholder commands that you can extend or override. When you invoke Cypress.Commands.add(), the engine validates the name against protected internal commands (like log or prompt) and throws informative errors if you attempt to shadow reserved functionality.
How to Create Cypress Custom Commands
Adding a Simple Command
The most common way to create a custom command is through Cypress.Commands.add(), which accepts a name, optional options, and a callback function.
// cypress/support/commands.ts
Cypress.Commands.add('login', (email: string, password: string) => {
cy.get('#email').type(email)
cy.get('#password').type(password)
cy.get('button[type=submit]').click()
})
Now any test can call cy.login('user@example.com', 'secret'). The registration logic in packages/driver/src/cypress/commands.ts (lines 88-95) ensures you cannot accidentally overwrite built-in commands unless they are explicitly whitelisted as placeholders.
Commands That Accept a Previous Subject
Some commands need to operate on elements yielded by previous commands. Specify the prevSubject option to create chainable commands.
Cypress.Commands.add(
'drag',
{ prevSubject: 'element' },
(subject, { x, y }) => {
cy.wrap(subject).trigger('mousedown')
cy.wrap(subject).trigger('mousemove', { clientX: x, clientY: y })
cy.wrap(subject).trigger('mouseup')
}
)
With prevSubject: 'element', the command only works when chained off a command that yields DOM elements, receiving the subject as the first argument.
Overwriting Existing Commands
To modify built-in behavior, use Cypress.Commands.overwrite(), which provides access to the original function.
Cypress.Commands.overwrite('visit', (originalFn, url, options) => {
const fullUrl = `${Cypress.env('BASE_URL')}${url}`
return originalFn(fullUrl, options)
})
The engine validates that the target command exists in builtInCommandNames before allowing the overwrite, preventing typos and undefined behaviors.
Creating Custom Queries
Cypress supports custom queries for reusable selectors via Cypress.Commands.addQuery().
Cypress.Commands.addQuery('getByTestId', (testId: string) => {
return () => cy.get(`[data-test-id="${testId}"]`)
})
This registers cy.getByTestId() as a first-class query command that integrates with Cypress's internal retry logic.
Placeholder Commands and Component Testing
The framework reserves specific command names as placeholders. In packages/driver/src/cypress/commands.ts (line 9), the PLACEHOLDER_COMMANDS array includes ['mount', 'hover'], allowing component testing adapters like @cypress/react to implement cy.mount without colliding with core commands.
Practical Examples
Login Command
// cypress/support/commands.ts
Cypress.Commands.add('login', (username, password) => {
cy.get('#username').type(username)
cy.get('#password').type(password)
cy.get('button[type=submit]').click()
})
// In your spec
it('authenticates user', () => {
cy.login('alice', 'pwd123')
cy.contains('Welcome Alice')
})
Drag and Drop with Previous Subject
Cypress.Commands.add(
'dragTo',
{ prevSubject: 'element' },
(subject, targetSelector) => {
const dataTransfer = new DataTransfer()
cy.wrap(subject).trigger('dragstart', { dataTransfer })
cy.get(targetSelector).trigger('drop', { dataTransfer })
cy.wrap(subject).trigger('dragend')
}
)
// Usage
cy.get('#source-item').dragTo('#target-container')
Overwriting Visit with Base URL
Cypress.Commands.overwrite('visit', (original, url, opts) => {
const base = Cypress.env('BASE_URL') || ''
return original(`${base}${url}`, opts)
})
Summary
- Cypress custom commands extend the
cynamespace throughCypress.Commands.add(), defined inpackages/driver/src/cypress/commands.ts. - Use the
prevSubjectoption to create commands that operate on elements yielded by previous commands. - Overwrite built-in commands using
Cypress.Commands.overwrite()to modify default behavior while preserving access to the original function. - Custom queries registered with
Cypress.Commands.addQuery()provide reusable selector logic that participates in Cypress's automatic retry mechanism. - Reserved names and built-in commands are protected by validation logic that throws descriptive errors when collision attempts occur.
Frequently Asked Questions
Can I overwrite built-in Cypress commands like cy.get or cy.click?
No. The registration engine in packages/driver/src/cypress/commands.ts maintains a builtInCommandNames list that prevents overwriting core commands. Only commands listed in PLACEHOLDER_COMMANDS (such as mount and hover) can be implemented or overwritten by user code or plugins.
What is the difference between Cypress.Commands.add() and Cypress.Commands.addQuery()?
Cypress.Commands.add() creates standard commands that perform actions or assertions, while Cypress.Commands.addQuery() creates custom queries designed to return elements for chaining. Queries retry automatically according to Cypress's default timeout settings, making them ideal for complex selector logic.
Where should I define custom commands in my project?
Define custom commands in cypress/support/commands.ts (or .js), which Cypress automatically imports before test files execute. For TypeScript projects, type definitions in cypress/support/index.d.ts ensure your IDE recognizes custom commands on the cy object.
Why do I get an error when trying to create a command named log or prompt?
These are reserved command names protected by internal validation logic at lines 88-95 of packages/driver/src/cypress/commands.ts. The framework reserves these names for internal use and throws an internalError if you attempt to register them, preventing conflicts with Cypress's logging and prompt systems.
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:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →