# How to Implement Custom CLI Commands in Magento 2: A Complete Developer’s Guide

> Learn to implement custom CLI commands in Magento 2. This guide details module creation, command class definition, DI injection, and command registration for efficient development.

- Repository: [Alessandro Ronchi/mageres](https://github.com/aleron75/mageres)
- Tags: how-to-guide
- Published: 2026-02-24

---

**To add a custom command to Magento 2’s CLI, create a module with proper registration files, define the command class extending Symfony’s `Command`, inject it via [`etc/di.xml`](https://github.com/aleron75/mageres/blob/main/etc/di.xml) using the `console.command` tag, and run `bin/magento setup:upgrade` to register it.**

The mageres repository curated by aleron75 catalogs essential Magento development resources, including patterns for extending the platform’s command-line interface. While the repository itself serves as a resource index rather than source code, the standard approach to implement custom CLI commands in Magento 2 relies on the framework’s built-in Symfony Console integration and object manager configuration.

## Module Structure and Registration

Every custom CLI command lives within a properly registered Magento module located in `app/code/Vendor/YourModule`. The framework discovers commands through a combination of module registration, dependency injection configuration, and class implementation.

Create the registration file at [`app/code/Vendor/YourModule/registration.php`](https://github.com/aleron75/mageres/blob/main/app/code/Vendor/YourModule/registration.php) to enable autoloading:

```php
<?php
use Magento\Framework\Component\ComponentRegistrar;

ComponentRegistrar::register(
    ComponentRegistrar::MODULE,
    'Vendor_YourModule',
    __DIR__
);

```

Declare the module metadata in [`app/code/Vendor/YourModule/etc/module.xml`](https://github.com/aleron75/mageres/blob/main/app/code/Vendor/YourModule/etc/module.xml):

```xml
<?xml version="1.0"?>
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xsi:noNamespaceSchemaLocation="urn:magento:framework:Module/etc/module.xsd">
    <module name="Vendor_YourModule" setup_version="1.0.0"/>
</config>

```

## Dependency Injection Configuration

Magento’s `\Magento\Framework\Console\CommandList` class collects all console commands during bootstrap. To register your command, inject it into the command list via [`etc/di.xml`](https://github.com/aleron75/mageres/blob/main/etc/di.xml) using the `console.command` virtual type pattern:

```xml
<?xml version="1.0"?>
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xsi:noNamespaceSchemaLocation="urn:magento:framework:ObjectManager/etc/config.xsd">
    <type name="Magento\Framework\Console\CommandList">
        <arguments>
            <argument name="commands" xsi:type="array">
                <item name="vendor:yourcommand" xsi:type="object">Vendor\YourModule\Console\Command\YourCommand</item>
            </argument>
        </arguments>
    </type>
</config>

```

The **name attribute** in the item tag serves as the unique identifier for the command instance within the object manager array.

## Implementing the Command Class

The command class must extend `\Symfony\Component\Console\Command\Command` and implement two critical methods: `configure()` for metadata definition and `execute()` for business logic.

Create [`app/code/Vendor/YourModule/Console/Command/YourCommand.php`](https://github.com/aleron75/mageres/blob/main/app/code/Vendor/YourModule/Console/Command/YourCommand.php):

```php
<?php
declare(strict_types=1);

namespace Vendor\YourModule\Console\Command;

use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Input\InputArgument;

/**
 * Example CLI command that greets a user.
 */
class YourCommand extends Command
{
    /** @var string */
    private const NAME = 'vendor:greet';

    /** @var string */
    private const DESCRIPTION = 'Greets the supplied name';

    protected function configure(): void
    {
        $this
            ->setName(self::NAME)
            ->setDescription(self::DESCRIPTION)
            ->addArgument(
                'name',
                InputArgument::OPTIONAL,
                'Name to greet',
                'World'
            );
    }

    protected function execute(InputInterface $input, OutputInterface $output): int
    {
        $name = $input->getArgument('name');
        $output->writeln("<info>Hello, {$name}!</info>");
        return Command::SUCCESS;
    }
}

```

### The configure() Method

Use `configure()` to define the command signature, description, and input expectations. Call `$this->setName()` to establish the command syntax (e.g., `vendor:greet`), and chain `addArgument()` or `addOption()` to specify input parameters.

### The execute() Method

The `execute()` method receives `InputInterface` and `OutputInterface` parameters. Extract input values via `$input->getArgument()` or `$input->getOption()`, implement your business logic, and use `$output->writeln()` for formatted feedback. Always return an integer exit code—typically `Command::SUCCESS` (0) or `Command::FAILURE` (1).

## Deployment and Verification

After implementing the files, execute the following sequence to activate the command:

```bash

# Register the module and update the database schema

bin/magento setup:upgrade

# Verify the command appears in the list

bin/magento list | grep vendor:greet

# Execute the command with an argument

bin/magento vendor:greet John

```

The output should display: `Hello, John!`

## Summary

- **Extend Symfony’s base class**: All Magento 2 CLI commands must extend `\Symfony\Component\Console\Command\Command` and implement `configure()` and `execute()` methods.
- **Configure DI in etc/di.xml**: Inject command classes into `\Magento\Framework\Console\CommandList` using the `commands` argument array.
- **Follow module structure**: Place files in `app/code/Vendor/Module` with proper [`registration.php`](https://github.com/aleron75/mageres/blob/main/registration.php) and [`etc/module.xml`](https://github.com/aleron75/mageres/blob/main/etc/module.xml) declarations.
- **Support dependency injection**: Use the constructor to inject Magento services like repositories, factories, or helper classes.
- **Test with bin/magento list**: Always verify registration before executing custom logic.

## Frequently Asked Questions

### Where should custom CLI command files be located in Magento 2?

Place command classes within the `Console/Command/` directory of your module, following the pattern [`app/code/Vendor/YourModule/Console/Command/YourCommand.php`](https://github.com/aleron75/mageres/blob/main/app/code/Vendor/YourModule/Console/Command/YourCommand.php). This PSR-4 compliant structure ensures Magento’s autoloader locates the class correctly when referenced in [`etc/di.xml`](https://github.com/aleron75/mageres/blob/main/etc/di.xml).

### How do I add optional flags and arguments to a custom CLI command?

In the `configure()` method, chain `$this->addArgument()` for positional parameters or `$this->addOption()` for named flags. Specify `InputArgument::REQUIRED`, `InputArgument::OPTIONAL`, or `InputOption::VALUE_OPTIONAL` to control input behavior, and provide default values as the fourth parameter.

### Can I inject Magento services into a custom CLI command constructor?

Yes, the object manager supports constructor dependency injection for CLI commands. Inject any service interface—such as `\Magento\Catalog\Api\ProductRepositoryInterface` or custom factories—into the constructor, and Magento instantiates the dependencies automatically when the command executes.

### Why does my custom command not appear in bin/magento list?

Verify three common issues: the module is enabled via `bin/magento module:status`, the [`etc/di.xml`](https://github.com/aleron75/mageres/blob/main/etc/di.xml) correctly references the command class FQCN in the `CommandList` arguments array, and `bin/magento setup:upgrade` has run successfully to clear generated code and rebuild the dependency injection container.