---
title: "Tutorial: create a console command from scratch"
manual: "TYPO3 Explained"
version: "main"
permalink: "https://docs.typo3.org/permalink/t3coreapi:console-command-tutorial@main"
source: "ApiOverview/CommandControllers/Tutorial.rst"
rendered: "2026-09-27T06:55:37+00:00"
---

# Tutorial: create a console command from scratch {#console-command-tutorial}

A console command is always inside an extension. If you want to create
one, [kickstart a custom extension](https://docs.typo3.org/permalink/t3coreapi:extension-kickstart@main) or use your
site package extension.

**Table of contents**

-   [Creating a basic command](https://docs.typo3.org/permalink/t3coreapi:creating-a-basic-command@main)
-   [Example console command implementations](https://docs.typo3.org/permalink/t3coreapi:example-console-command-implementations@main)

## Creating a basic command {#console-command-tutorial-create}

In this section we will create an empty command skeleton with no parameters or user
interaction.

> [!NOTE]
> **See also**
>
> -   [Create a new console command with "Make"](https://docs.typo3.org/permalink/t3coreapi:extension-make-console-command@main).

Create a class called `DoSomethingCommand` which extends
`\Symfony\Component\Console\Command\Command`.

**EXT:my_extension/Classes/Command/MyCommand.php**

```php
<?php

declare(strict_types=1);

namespace MyVendor\MyExtension\Command;

use Symfony\Component\Console\Attribute\AsCommand;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Style\SymfonyStyle;

#[AsCommand(
  name: 'myextension:dosomething',
  description: 'A command that does nothing and always succeeds.',
  aliases: ['examples:dosomethingalias'],
)]
class DoSomethingCommand extends Command
{
  protected function configure(): void
  {
    $this->setHelp('This command does nothing. It always succeeds.');
  }

  protected function execute(InputInterface $input, OutputInterface $output): int
  {
    $io = new SymfonyStyle($input, $output);
    $io->info('Command needs to be implemented. ');
    return Command::SUCCESS;
  }
}

```

The following two methods should be overridden by your class:

-   **`configure()`**

    As the name suggests, this is where the command can be configured.
    Add a help text and/or define arguments and options.

-   **`execute()`**

    Contains the command logic. Must return an integer. It is considered best
    practice to return the constants
    `Command::SUCCESS` or `Command::FAILURE`.

The above example can be run via the command line. If a newly created or edited
command is not found, clear the cache first:

**Composer mode**

```bash
vendor/bin/typo3 cache:flush
vendor/bin/typo3 examples:dosomething
```

**Classic mode**

```bash
typo3/sysext/core/bin/typo3 cache:flush
typo3/sysext/core/bin/typo3 examples:dosomething
```

The command will return without a message as it does nothing but state that it
has succeeded.

## Example console command implementations {#console-command-tutorial-example}

### A command with parameters and arguments {#console-command-tutorial-parameters}

**packages/my_extension/Classes/Command/SendFluidMailCommand.php**

```php
<?php

declare(strict_types=1);

namespace MyVendor\MyExtension\Command;

use Symfony\Component\Console\Attribute\AsCommand;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Style\SymfonyStyle;
use T3docs\Examples\Exception\InvalidWizardException;
use TYPO3\CMS\Core\Attribute\AsNonSchedulableCommand;

#[AsCommand(
  name: 'myextension:createwizard',
)]
#[AsNonSchedulableCommand]
final class CreateWizardCommand extends Command
{
  protected function configure(): void
  {
    $this
        ->setHelp('This command accepts arguments')
        ->addArgument(
          'wizardName',
          InputArgument::OPTIONAL,
          'The wizard\'s name',
        )
        ->addOption(
          'brute-force',
          'b',
          InputOption::VALUE_NONE,
          'Allow the "Wizard of Oz". You can use --brute-force or -b when running command',
        );
  }
  protected function execute(
    InputInterface $input,
    OutputInterface $output,
  ): int {
    $io = new SymfonyStyle($input, $output);
    $wizardName = $input->getArgument('wizardName');
    $bruteForce = (bool)$input->getOption('brute-force');
    try {
      $this->doMagic($io, $wizardName, $bruteForce);
    } catch (InvalidWizardException) {
      return Command::FAILURE;
    }
    return Command::SUCCESS;
  }

  private function doMagic(SymfonyStyle $io, mixed $wizardName, bool $bruteForce): void
  {
    // do your magic here
  }
}

```

It uses attribute `#[AsNonSchedulableCommand]` so that it can only be used
from the console and not from the **Administration > Scheduler** module.

This command takes one argument `wizardName` (optional) and one option (optional),
which can be added on the command line:

**Composer mode**

```bash
vendor/bin/typo3 examples:createwizard [-b] [wizardName]
```

**Classic mode**

```bash
typo3/sysext/core/bin/typo3 examples:createwizard [-b] [wizardName]
```

### Sending a `FluidMail` via command {#console-command-tutorial-fluidmail}

**packages/my_extension/Classes/Command/SendFluidMailCommand.php**

```php
<?php

declare(strict_types=1);

namespace T3docs\Examples\Command;

use Symfony\Component\Console\Attribute\AsCommand;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use TYPO3\CMS\Core\Core\Bootstrap;
use TYPO3\CMS\Core\Core\SystemEnvironmentBuilder;
use TYPO3\CMS\Core\Http\ServerRequest;
use TYPO3\CMS\Core\Mail\FluidEmail;
use TYPO3\CMS\Core\Mail\MailerInterface;
use TYPO3\CMS\Core\Site\SiteFinder;

#[AsCommand(
  name: 'myextension:sendmail',
)]
class SendFluidMailCommand extends Command
{
  public function __construct(
    private readonly SiteFinder $siteFinder,
    private readonly MailerInterface $mailer,
  ) {
    parent::__construct();
  }

  protected function execute(InputInterface $input, OutputInterface $output): int
  {
    Bootstrap::initializeBackendAuthentication();

    // The site has to have a fully qualified domain name
    $site = $this->siteFinder->getSiteByPageId(1);
    $request = (new ServerRequest())
        ->withAttribute('applicationType', SystemEnvironmentBuilder::REQUESTTYPE_FE)
        ->withAttribute('site', $site);
    $GLOBALS['TYPO3_REQUEST'] = $request;
    // Send some mails with FluidEmail
    $email = new FluidEmail();
    $email->setRequest($request);
    // Set receiver etc
    $this->mailer->send($email);
    return Command::SUCCESS;
  }
}

```
