Tutorial: Create a console command from scratch 

A console command is always inside an extension. If you want to create one, kickstart a custom extension or use your site package extension.

Creating a basic command 

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

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

EXT:my_extension/Classes/Command/MyCommand.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;
  }
}
Copied!

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:

vendor/bin/typo3 cache:flush
vendor/bin/typo3 examples:dosomething
Copied!
typo3/sysext/core/bin/typo3 cache:flush
typo3/sysext/core/bin/typo3 examples:dosomething
Copied!

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

Example console command implementations 

A command with parameters and arguments 

packages/my_extension/Classes/Command/SendFluidMailCommand.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;

#[AsCommand(
  name: 'myextension:createwizard',
)]
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
  }
}
Copied!

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

vendor/bin/typo3 examples:createwizard [-b] [wizardName]
Copied!
typo3/sysext/core/bin/typo3 examples:createwizard [-b] [wizardName]
Copied!

Sending a FluidMail via command 

packages/my_extension/Classes/Command/SendFluidMailCommand.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;
  }
}
Copied!