Drush (9) Command for own module
  1. execute drush generate drush-command-file.

  2. drush now asks for the module name, if the module already has a composer.json drush asks if it should overwrite it.

If there is a composer.json we ask drush not to overwrite it, but to add the following to our composer.json after drush has generated the files

   "extra": {
    "drush": {
      "services": {
        "drush.services.yml": "^9"

Drush has now created 2 files for us

Once the src/Commands/{ModulName}Commands.php this looks like this ->

Important here is the annotation of the functions:

@command {name of the command e.g. cache:clear}
@aliases {comma separated command aliases e.g. cc,cache-clear,cr}
@usage {description of how the command is executed}

namespace Drupal\{module name}\Commands;

use Consolidation\OutputFormatters\StructuredData\RowsOfFields;
use Drush\Commands\DrushCommands;

 * In addition to a command file like this, you need a drush.services.yml
 * in the root directory of your module, and a composer.json file containing the name
 * of the service file to use.
 * An example of how to include Drupal services can be found in these files:
 * - http://cgit.drupalcode.org/devel/tree/src/Commands/DevelCommands.php
 * - http://cgit.drupalcode.org/devel/tree/drush.services.yml
class {ModuleName}Commands extends DrushCommands {

   * Command description here.
   * @command {moduleName}-commandName.
   * @param $arg1 Description of the argument.
   * @param array $options An associative array of options whose values come from cli, aliases, config, etc.
   * @option option-name Option description.
   * @usage {module-name}-commandName foo
   * Usage description
   * @aliases foo
  public function command-name($arg1, $options = ['option-name' => 'default']) {
    $this->logger()->success(dt('Result unlocked.'));

   * An example of the table output format.
   * @command {modulename}-token
   * @param array $options An associative array of options whose values come from cli, aliases, config, etc.
   * @aliases token
   * @field-labels
   * group: group
   * token: token
   * name: name
   * @default-fields group,token,name
   * @return \Consolidation\OutputFormatters\StructuredData\RowsOfFields.
  public function token($options = ['format' => 'table'])
    $all = \Drupal::token()->getInfo();
    foreach ($all['tokens'] as $group => $tokens) {
      foreach ($tokens as $key => $token) {
        $rows[] = [
          'group' => $group,
          'token' => $key,
          'name' => $token['name'],
    return new RowsOfFields($rows);


Now we have the drush.services.yml which usually does not need to be touched.

  {module name}.commands:
    class: \{modulename}.commands {modulename}.commands
      - { name: drush.command }

Once you have implemented everything you still need to run drush cr so that your command is recognized by drush.


  1. if you want to write a global drush command, you have to change the namespace (\Drush\Commands[dir-name]).
  2. the command file must end with Commands.php (e.g. MyTollesCommandCommands.php).
  3. the folder where the file is located must be named Commands.
  4. the following folders belong to the global drush area (../drush, /drush and /sites/all/drush).

Source: http://docs.drush.org/en/master/commands/#creating-custom-drush-commands