Drupal 11.4 brings one of the most significant developer experience improvements in recent years: a built-in command-line interface that can discover commands provided by Drupal modules.
Until now, Drupal Core included a limited CLI (core/scripts/drupal) that only supported a handful of core commands. Most developers relied entirely on Drush for command-line workflows.
Starting with Drupal 11.4, Core introduces a new executable named dr, laying the foundation for a modern, Symfony-based CLI built directly into Drupal.
What changed?
The previous CLI entry point:
core/scripts/drupal
is now deprecated.
The new entry point is:
core/scripts/dr
and is automatically installed into Composer's bin directory:
vendor/bin/dr
If you're using DDEV, the recommended way to execute commands is:
ddev exec dr
Why this matters
This is much more than a filename change.
The new CLI allows Drupal modules to provide their own commands without relying on Drush.
Commands are automatically discovered using Symfony Attributes, making command development significantly cleaner and more aligned with modern PHP practices.
This also means Drupal Core is moving closer to Symfony's native Console component instead of maintaining a separate command discovery mechanism.
Discovering commands with AsCommand
Instead of manually registering commands and using BootableCommandTrait, commands are now discovered using Symfony's AsCommand attribute.
Before
final class RecipeInfoCommand extends Command {
use BootableCommandTrait;
public function __construct($class_loader) {
parent::__construct('recipe:info');
}
protected function execute() {
$this->boot();
}
}
After
#[AsCommand(
name: 'recipe:info',
description: 'Shows information about a recipe.',
)]
final class RecipeInfoCommand extends Command {
public function __construct(
#[Autowire(service: 'class_loader')]
protected ClassLoader $classLoader,
) {
parent::__construct();
}
protected function execute(
InputInterface $input,
OutputInterface $output,
): int {
// Command implementation.
return Command::SUCCESS;
}
}
The new implementation is:
- cleaner
- fully typed
- attribute-based
- compatible with Symfony best practices
Goodbye BootableCommandTrait
Drupal\Core\Command\BootableCommandTrait has been deprecated.
Commands no longer need to explicitly boot Drupal before execution. The new CLI handles the application lifecycle automatically, simplifying command implementations considerably.
Dependency Injection becomes easier
The new CLI embraces Symfony's Dependency Injection features.
Instead of manually retrieving services, commands can receive dependencies directly through constructor injection using the Autowire attribute.
For example:
#[Autowire(service: 'class_loader')]
protected ClassLoader $classLoader
This makes command classes easier to test and more consistent with modern Drupal service development.
Return proper Symfony status codes
Commands should now return Symfony command constants instead of integers:
return Command::SUCCESS;
or
return Command::FAILURE;
This improves readability and follows Symfony Console conventions.
Built-in commands
Drupal Core already ships with several commands demonstrating the new architecture.
Examples include:
vendor/bin/dr cache:rebuild
vendor/bin/dr system:status
vendor/bin/dr system:cron
vendor/bin/dr recipe:apply
vendor/bin/dr recipe:info
You can view all available commands with:
vendor/bin/dr list
Command namespaces
Module authors should namespace their commands using the module name.
For example:
Module:
my_module
Command:
my-module:import
Hyphens should replace underscores in both the namespace and command name.
The following namespaces are reserved for Drupal Core:
- no namespace
core- core module namespaces
Using these may lead to future conflicts.
Logging support
The new CLI writes output through Drupal's console logger:
logger.console
using:
Drupal\Core\Command\DrupalConsoleLogger
This allows command output to integrate with Drupal's logging system.
Experimental status
Although the CLI is available in Drupal 11.4, it is still considered:
- @internal
- experimental
The Drupal community expects additional commands and APIs to evolve over upcoming releases.
Developers are encouraged to experiment with the new architecture while avoiding assumptions about long-term API stability.
What about Drush?
This announcement does not replace Drush.
Drush remains the most feature-rich command-line tool in the Drupal ecosystem, offering hundreds of commands for:
- configuration management
- database operations
- deployments
- migrations
- debugging
- development workflows
The new Core CLI currently focuses on providing a standardized command infrastructure within Drupal itself.
In the future, we may see increasing convergence between Drupal Core and Drush, but for now they serve complementary purposes.
Final thoughts
The introduction of vendor/bin/dr marks an important milestone in Drupal's modernization efforts.
By embracing Symfony Console, PHP Attributes, constructor-based dependency injection, and automatic command discovery, Drupal Core significantly improves the developer experience while reducing boilerplate code.
For module developers, creating CLI commands is now simpler, cleaner, and more consistent with the broader PHP ecosystem.
While still experimental, the new CLI establishes a solid foundation for future Core tooling and opens exciting possibilities for contributed modules without requiring Drush integration.
If you're targeting Drupal 11.4 or newer, now is an excellent time to start exploring dr and preparing your custom commands for the next generation of Drupal CLI development.
Comments