Custom commands extend Cypress's cy.* API with reusable actions defined via Cypress.Commands.add. They reduce duplication for common flows like login, and can be chained like built-in commands.
In cypress/support/commands.js you write Cypress.Commands.add('login', (user, pass) => {...}), then call cy.login('qa', 'pw') anywhere. Custom commands can be parent commands (start a chain), child commands (chain off a subject, e.g. cy.get(...).customThing()), or dual. They centralize repeated logic (auth, navigation, form filling) and make tests read at a higher level of intent. Overusing them for one-off logic hurts readability; reserve them for genuinely shared, stable flows. TypeScript users add type definitions so custom commands autocomplete.
cy.login(user, pass) — defined once — replaces 15 lines of form-filling repeated across dozens of specs, and can internally use the fast API login so every test benefits from the optimization in one place.
The team wraps every single cy.get in a custom command 'getEl'. Why can this be an anti-pattern?