> ## Documentation Index
> Fetch the complete documentation index at: https://kawax.biz/llms.txt
> Use this file to discover all available pages before exploring further.

# Registering package Artisan commands and resolving dependencies

> Trace the path from commands() to Artisan startup in Laravel 13, and learn about separating command construction from execution, dependency injection, deferred providers, and long-term maintenance considerations.

When you want to add Artisan commands for diagnostics, synchronization, or installation to your package, think about command registration, instance construction, and execution as separate steps. If you resolve services that are only needed at execution time in the constructor, you may end up affecting the startup of unrelated Artisan commands.

This page examines the implementation in Laravel Framework `v13.35.0` and goes deeper into the `commands()` registration example from [Package development basics](/en/advanced/package-development). For implementing file generation, see [GeneratorCommand](/en/advanced/generator-command); for participating in optimization tasks, see [Integrating with optimize](/en/advanced/package-optimization).

## Register commands explicitly from your provider

Commands in a package are registered by class name in the service provider's `boot()` method. Auto-discovery of the application's `app/Console/Commands` and registration of classes provided by a package are separate entry points.

```php theme={null}
<?php

namespace Acme\Courier;

use Acme\Courier\Console\StatusCommand;
use Illuminate\Support\ServiceProvider;

class CourierServiceProvider extends ServiceProvider
{
    public function boot(): void
    {
        if ($this->app->runningInConsole()) {
            $this->commands([
                StatusCommand::class,
            ]);
        }
    }
}
```

This example assumes that the provider is loaded through [package discovery](/en/advanced/package-discovery) or by explicit registration on the application side. Simply placing a command class in your package does not automatically register the package's provider.

`runningInConsole()` checks whether the application is running in a console environment, not whether a particular command is being executed. If you also support calling `Artisan::call()` from within HTTP requests, account for the possibility that this condition prevents registration, and define the usage contract, including whether to register unconditionally. For running commands programmatically, see [Artisan](/en/artisan).

## commands() does not run the command on the spot

`ServiceProvider::commands()` registers a closure that uses the given class names with `Illuminate\Console\Application::starting()`. At this point, the command's `handle()` is not called.

With the standard Console Kernel, the registered startup callbacks run later when the Artisan Application is created. There, `resolveCommands()` passes each class to `resolve()` and proceeds with command registration.

```mermaid theme={null}
flowchart TD
    A["Provider boot()"] --> B["commands() registers a startup callback"]
    B --> C["Console Kernel creates the Artisan Application"]
    C --> D["resolveCommands() registers the classes"]
    D --> E["Regular classes are instantiated by the container"]
    E --> F["The selected command runs"]
    F --> G["handle() dependencies are resolved and it is called"]
```

The key point here is that **a command is not necessarily instantiated at the same time it is executed**. For regular classes that do not use the dedicated deferred registration path described later, `resolve()` calls the container's `make()`, so the constructor and its dependencies are resolved at registration time.

As a result, the constructor of a package command may run even when you only execute `php artisan list` or some other command. Placing `commands()` in `boot()` does not by itself guarantee that instantiation is delayed until execution.

## Inject runtime dependencies into handle()

As an example, let's create a command that only displays whether the package configuration is enabled or disabled. As in the official Laravel 13 Artisan documentation, the name and description are defined with the `Signature` and `Description` attributes.

```php theme={null}
<?php

namespace Acme\Courier\Console;

use Illuminate\Console\Attributes\Description;
use Illuminate\Console\Attributes\Signature;
use Illuminate\Console\Command;
use Illuminate\Contracts\Config\Repository;

#[Signature('courier:status')]
#[Description('Display whether the Courier integration is enabled')]
class StatusCommand extends Command
{
    public function handle(Repository $config): int
    {
        $enabled = (bool) $config->get('courier.enabled', false);

        $this->line($enabled ? 'Courier: enabled' : 'Courier: disabled');

        return self::SUCCESS;
    }
}
```

This example does not add a constructor and instead receives the config Repository as an argument to `handle()`. Because `Command::execute()` uses the container's `call()` on the command's execution path, type-hinted dependencies are injected.

When you use external API clients or synchronization services, you can inject them in the same place if they are only needed at execution time. However, if constructing the service itself or another provider's `boot()` has side effects, those are not automatically prevented.

| Location | When it is evaluated for a regular command | Suitable for |
| - | - | - |
| Provider `boot()` | When the provider boots | Registering command classes |
| Command constructor | At instantiation, which is not necessarily right before execution | Lightweight initialization |
| Type-hinted `handle()` arguments | When the command runs | Resolving services needed for execution |
| `handle()` body | When the command runs | Input validation, business logic, displaying results |

<Warning>
  Do not connect to external APIs, update the database, or overwrite published files in the constructor. Package users need to be able to use `list` and `help` right after installation, even when connection settings are not yet complete.
</Warning>

If you define your own constructor, also call `parent::__construct()`. Laravel's parent constructor configures the command from attributes, the signature, the description, and so on.

## Signature and deferred registration are separate mechanisms

The `Application::resolve()` we examined has a branch that reads the `name` of Symfony's `AsCommand` attribute and saves the mapping between the name and the class in a command map. In this branch, the container's `make()` is not called on the spot. The Console Kernel later calls `setContainerCommandLoader()` to set up a loader that uses this map.

On the other hand, Laravel's `Signature` attribute used in the example above is separate from this `AsCommand` check. **Adding `Signature` alone does not put the class on this deferred registration path.**

Treat this difference as something to keep in mind when reading the internal implementation. Start by keeping construction cost and side effects small with the official command definition and dependency injection into `handle()`. Even when a deferred loader is present, command information may be needed for listings, help, and so on, so do not design on the assumption that "the command is never instantiated except when it is selected and run."

## Do not defer command registration itself

Lazily loading service bindings and making a command known to Artisan are two different things. If a provider implements `DeferrableProvider`, its `boot()` may not run at all until the target service is needed.

If you put command registration only in that `boot()` and the provider is not loaded before Artisan starts, the startup callback that announces the command is not registered either. Even if a service you moved into `handle()` dependency injection would load the deferred provider, that does not solve the problem that occurs before Artisan finds the command.

To make sure commands are exposed, you can place registration in an eagerly loaded provider and separate only the heavy service bindings into a different deferred provider. For the specific requirements, see [Deferred service providers](/en/advanced/deferred-provider).

## What to check when updating

Command names, arguments, options, output, and exit codes are public interfaces used by your users' shell scripts and CI. Do not change them as casually as internal class changes; check compatibility and migration steps.

| Case | What to check |
| - | - |
| Installing into a clean application | The provider is loaded and the command name can be found |
| `php artisan list` | The package's execution logic and external connections do not start |
| `php artisan help courier:status` | The description and input specification can be viewed |
| Actually running the command | `handle()` dependencies are resolved and the intended exit code is returned |
| Configuration not published or not set | Listing and help do not break, and missing configuration is handled appropriately at execution time |
| Updating the package or Laravel | Existing command names, arguments, and options continue to work |

For the `StatusCommand` above, you can verify the output and exit code as follows in a test environment where the provider is loaded. For the testing setup, see [Package testing](/en/advanced/package-testing); for details on assertions, see [Console Tests](/en/console-tests).

```php theme={null}
public function test_status_command_displays_enabled_state(): void
{
    config()->set('courier.enabled', true);

    $this->artisan('courier:status')
        ->expectsOutput('Courier: enabled')
        ->assertExitCode(0);
}
```

In addition, for commands that use heavy services, also verify that the service's business methods are not called when running listing or help. Checking only that a command can be registered will not detect its impact on unrelated commands.

## Primary sources

* [Laravel official documentation: Registering package commands](https://github.com/laravel/docs/blob/13.x/packages.md#commands)
* [Laravel official documentation: Command structure and dependency injection](https://github.com/laravel/docs/blob/13.x/artisan.md#command-structure)
* [ServiceProvider: commands()](https://github.com/laravel/framework/blob/v13.35.0/src/Illuminate/Support/ServiceProvider.php)
* [Console Application: starting(), bootstrap(), and resolve()](https://github.com/laravel/framework/blob/v13.35.0/src/Illuminate/Console/Application.php)
* [Console Kernel: getArtisan() and loader registration](https://github.com/laravel/framework/blob/v13.35.0/src/Illuminate/Foundation/Console/Kernel.php)
* [Command: constructor and execute()](https://github.com/laravel/framework/blob/v13.35.0/src/Illuminate/Console/Command.php)
* [Official tests: command resolution and alias registration with AsCommand](https://github.com/laravel/framework/blob/v13.35.0/tests/Console/ConsoleApplicationTest.php)


## Related topics

- [Laravel Package Development](/en/advanced/package-development.md)
- [Artisan console](/en/artisan.md)
- [Package service resolution hooks](/en/advanced/package-service-resolution.md)
- [GeneratorCommand — Implementing custom make: commands](/en/advanced/generator-command.md)
- [Overriding and updating package views](/en/advanced/package-views.md)


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.