occ commands

Nextcloud apps can register custom occ commands that administrators can run from the command line. Commands are plain PHP classes annotated with the #[AsCommand] attribute from OCP\Console. Nextcloud wires the attribute on top of Symfony Console for you, so you get argument parsing, bash completion, and formatted output without extending a base class.

Registering a command

List every command class in appinfo/info.xml under the <commands> element:

Nextcloud reads this list at startup and wires each class through the dependency injection container, so constructor injection works automatically.

Creating a command class

Place command classes in lib/Command/. Add a #[AsCommand] attribute to the class and implement a single __invoke() method that runs the command:

The class itself needs no constructor call and no parent class, the constructor is only used for dependency injection.

Multiple commands in one class

To group related commands that share dependencies, put #[AsCommand] on individual public methods instead of on the class. Nextcloud registers one occ command per attributed method:

Only OCA\MyApp\Command\UserCommands needs to be listed in appinfo/info.xml, both myapp:user:create and myapp:user:delete are registered from it.

Command naming

Use appid:command-name as the command name. For apps with many commands, group them with an extra segment: appid:group:command-name. Names must be lowercase and use hyphens as word separators.

Arguments and options

Arguments are positional and declared with the #[Argument] attribute on a parameter of __invoke(). Options are prefixed with -- and declared the same way with #[Option]. Both attributes accept a description (shown in --help) and an optional name; when name is omitted it defaults to the parameter's own name. #[Option] also accepts a single-letter shortcut, for example shortcut: 'b' for -b.

The parameter's type and default value — not the attribute — decide whether the argument or option is required, repeatable, or a flag:

Parameter declaration

Behavior

string $name

Required — the command fails if it is not provided.

string $name = 'foo'

Optional, defaults to 'foo'.

?string $name = null

Optional and nullable, defaults to null.

array $name = []

Repeatable — the value can be passed multiple times and is collected into an array. A variadic argument (string ...$name) behaves the same way.

bool $name = false

Flag (option only) — absent means false, present means true.

bool $name = true

Flag (option only) that is on by default; pass --no-name to disable it.

Since PHP parameter names are camelCase but Nextcloud's naming convention for options and arguments uses lowercase with hyphens, override the default name for such parameters, for example #[Option(name: 'object-store')] ?string $objectStore = null.

The arguments and options handling in Nextcloud covers the common cases of the Symfony Console component. Consult its documentation for background on how Symfony itself models arguments and options.

Output, input, and other helpers

Besides #[Argument] and #[Option] parameters, __invoke() can request the following types by type-hint alone — no attribute needed, and the parameter order does not matter:

Return codes

__invoke() (or an attributed method) must return an OCP\Console\ExitCode case, or a plain integer:

Returning the enum case is recommended; declare the method's return type as ExitCode.

Interactive commands

Commands can ask for confirmation or prompt for values using Symfony's question helper. Request an OCP\Console\IQuestionHelper as a parameter of __invoke(), the same way as IOutput:

use OCP\Console\ExitCode;
use OCP\Console\IOutput;
use OCP\Console\IQuestionHelper;
use Symfony\Component\Console\Question\ConfirmationQuestion;

class Greet {
    public function __invoke(
        IOutput $output,
        IQuestionHelper $questionHelper,
    ): ExitCode {
        $question = new ConfirmationQuestion('Are you sure? (y/n) ', false);

        if (!$questionHelper->ask($question)) {
            $output->writeln('Aborted.');
            return ExitCode::Failure;
        }

        // ...
        return ExitCode::Success;
    }
}