Skip to main content

Drupal CLI in core finally landed in 11.4, how and why we got here

Drupal 11.4 includes dr, an experimental command-line interface in core that discovers Symfony Console commands from core and contrib modules. The article explains why Drupal needs an internal CLI, why the attempts of 2014 and 2019 stalled, and which changes in 2026 allowed it to land. It then describes what dr runs in 11.4, the options for making contrib commands work with both Drush and dr, why deployments still depend on Drush, and why remote site management based on site aliases and sql:sync will likely need a different workflow.

Drush is part of almost every serious Drupal project: it rebuilds caches, runs database updates, imports configuration and drives deployments. With Drupal 11.4, core ships its own command-line tool, dr, and the initiative behind it openly aims to replace Drush over time. The tool is still experimental, but the transition has started, and the parts of daily work that depend on Drush, especially CI/CD pipelines and remote operations, will not all move at the same pace. Knowing where things stand now avoids being wrong-footed later.

Drupal has depended on an external tool for its daily operations at a cost

Drush is the de facto CLI for Drupal. All CLI operations (rebuilding caches, running database updates, importing configuration, driving deployments) go through it, and it lives outside core. Core's own script, core/scripts/drupal, added in 2018 (#2911319), was not extensible and only shipped three built-in commands: quick-start, install and generate-theme.

Drush logo

Discoverability was one of the problems: a Drupal CLI only appears after requiring the package drush/drush, so it is not obvious that a Drupal CLI tool exists for newcomers. But the real burden is keeping two separate projects in step, Drush and Drupal core. Every Symfony upgrade in core forced a new Drush release and broke Composer updates, as happened with Drupal 8.4 and Symfony 3, and again with Drupal 9 and Symfony 4. Sites had to wait for Drush to catch up.

An internal CLI would be easy to find for everyone and removes the version-sync problem. As catch, a core committer, put it in "CLI entry point in Drupal Core"'s comment #47: "drush also has to support multiple versions of core, but a command in core only has to support the version it's in (e.g. an command in 11.x doesn't need to work with 12.x or 10.x)." Commands in core also follow the core tests and backwards compatibility policy. And this internal CLI provides one standard way to provide commands, instead of choosing between Drush or custom scripts (or Drupal Console when it was in use).

The counterargument made sense as well. Drush is proven and widely used, so why rebuild it? "Instead of reinventing the wheel we could promote Drush instead or even include it in our composer.json as a requirement" hchonov wrote in 2019 (#3089277).

Finally, core chose its own CLI because a Composer dependency on Drush creates a two-way coupling: core changes can break Drush, and Drush has to follow core's Symfony versions. Core commands only depend on core. Agreeing on that direction, though, took a long time.

Ten years of the same debates

The first attempt (#2242947) was opened in 2014 and collected 214 comments before it was closed as outdated in 2026. The second (#3089277) was opened in 2019 in the Ideas queue and ended in "the same kind of inertia".

Both were blocked by the same recurring issues, and each position was reasonable at the time:

  • Overlap with Drush: core did not want two approaches, and Drush had not committed to using a core CLI.
  • Release timing: the work was deferred to 8.1.x to avoid disrupting the Drupal 8 release, then pushed from minor to minor.
  • Name: the drupal binary was already taken by Drupal Console, which could not be renamed without breaking its users.
  • Bootstrap: commands needed to run with and without a booted Drupal, and the kernel was tightly coupled to HTTP.
  • Discovery: the design never converged between plugins, services, separate containers and composer extra.
  • Ownership: there was broad agreement that it was a good idea, but nobody to implement and drive it.

By 2022, even Moshe Weitzman, the lead Drush maintainer, backed bundling Drush in the project templates instead of a core CLI. Four years later, every one of these blockers had an answer.

What changed in 2026

Moshe probably laid the first stone with his post Drush's Final Act in February. He stated "attempts stalled due to debates about command name or bootstrap details or numerous other bikeshed details."

He made the competitive pressure explicit: "I think it is vital for Drupal from a competitive perspective to get to a full featured CLI quickly." He also stated his goal for the tool he maintains: "My end goal here is the death of Drush. It has served an incredibly long and useful life. Its death will be its most impactful and generous act."

The technical blockers fell first. Drush converted all its commands to vanilla Symfony Console, so any Symfony Console application can discover them. Drupal Console was officially sunset in April 2025, which removed the second competing tool. Its maintainers framed the sunset as a success, since Drush absorbed its interactive features and modern Symfony components: "So the project is sunsetting, not because it failed, but because it succeeded" (Drupal Console project page), something we can probably agree on.

Drupal Dev Days Athens 2026 group picture
Group picture of Drupal Dev Days Athens 2026, one of the events where the community talked about all of this. Source: https://www.flickr.com/photos/204480346@N05/55226423971/in/pool-dddathens2026/

Bootstrap stopped being a blocker as well. Only a few commands, like site:install, need a non-booted site but it was thought this required a lot of complexity to support. However, greg.1.anderson showed that overriding Application::find(), about a dozen lines and the same approach Drush has used since Drush 9, lets bootstrapped and non-bootstrapped commands share one script, avoiding that complexity. Core also adopted symfony/runtime (#3313404), which moves environment setup out of DrupalKernel into a reusable layer shared by HTTP and CLI entry points, reducing the custom bootstrap code each had to maintain separately.

Discovery settled on a standard Symfony mechanism: a compiler pass picks up classes in src/Command/ with #[AsCommand], with no custom design needed.

Finally, the people aligned. In the issue, Dries Buytaert confirmed agreement: "There is agreement that a system-wide CLI would be a good thing for Drupal." The CLI in Core community initiative (#3582246, #cli-in-core on Drupal Slack) formed around that agreement, with the Drush lead, core committers and a roadmap.

Disagreements still came up, and each was settled on its own. Moshe first proposed depending on Drush temporarily to move fast. catch raised the risk of getting stuck halfway with a two-way dependency, Moshe agreed to drop it, and the plan became bringing the essential commands into core one by one. The name, with dex, drupal and dr on the table, went to its own policy issue (#3583795) so it would not block the code. The result was dr, in line with short CLIs like wp, ng or gh.

Another question was timing. Shipping in 11.x won over waiting for Drupal 12. In longwave's words (#125), shipping early means "users can slowly migrate to dr commands while keeping the drush fallback for the time being, and secondly, that adding the CLI in 11.x inspires people to open MRs to add their favourite/most used commands in time for Drupal 12."

The timeline shows how quickly things moved once the pieces were in place. dpi opened (#3453474) in June 2024. Initial work was done, but it stalled for about 18 months. Moshe revived it in February 2026, and larowlan committed it on June to main, 11.x and 11.4.x: "Committed to main and 11.x. Backported to 11.4.x. Congrats all!" (#144).

What dr ships in Drupal 11.4

The new CLI runs as vendor/bin/dr, implemented in core/scripts/dr. In DDEV, ddev exec dr works because vendor/bin is in the web container PATH. It discovers Symfony Console commands in core and contrib through the Symfony Runtime component, so caches can be rebuilt and cron can run without Drush. core/scripts/drupal is deprecated in favour of vendor/bin/dr.

The main commands available in 11.4.0 are the ones the previous core script provided: generate-theme, install and quick-start, along with cache:rebuild, content:export, system:cron, system:status and commands for recipes (recipe:apply and recipe:info). Several map directly to familiar Drush commands:

drush cr        →  dr cr          # Alias of cache:rebuild
drush cron      →  dr cron        # Alias of system:cron
drush status    →  dr status      # Alias of system:status
drush si        →  dr install     # install using a profile or a recipe
drush recipe    →  dr recipe      # Alias of recipe:apply

dr is marked @internal and experimental in the change record, and the roadmap to stable (#3606055) is still active. Drush keeps working during the transition, which leaves time for contrib modules to adapt.

Contrib commands: the options at a glance

Writing and porting commands is already documented in the official Drush command porting guide to the new dr Drupal core CLI and in the change record. The model fits in one line: a plain Symfony Console command, a class with #[AsCommand] in the module's src/Command/ directory, auto-discovered by core, with no drush.services.yml.

There are two ways to support both tools during the transition. The first is a plain Symfony Console command, which both Drush and dr discover. Recent Drush already uses Symfony Console commands, so many need little or no work. The second is the Service Wrapper Pattern, for commands that rely on Drush features such as output formatters (--format). The business logic goes in a Drupal service. Two thin command classes call it: one for dr (a plain Symfony Console command in src/Command/, auto-discovered), one for Drush (in src/Drush/Commands/, registered in drush.services.yml). Neither wrapper contains logic, so nothing is duplicated.

Local commands are the easy part, though.

Remote site management will likely need a new workflow

The biggest difference from the Drush model is where the site the tool manages sits: dr works only against the local Drupal instance. It has no site aliases, and the roadmap to stable does not list any remote command. Because remote commands depend on site aliases, and aliases are not planned, remote commands will most likely not be covered by dr either.

Moshe, who is also the technical lead of the initiative, expects aliases to go: "Along the way, we may lose some niceties like Drush config and Site aliases. Platforms already have solutions for replacements aliases such as Lagoon (Amazee), Terminus (Pantheon), acli (Acquia), etc." He also points to generic tools such as Pssh, Fabric or ash, a Drush-centric alias tool by jonpugh.

Managing remote sites will probably require a shift or adaptation in the workflow or tooling, for example running dr inside each environment over SSH, in the container or through the hosting platform CLI or a module or tool in the contrib space, instead of targeting it from a local machine with an alias. Daily CI/CD work is directly affected: drush sql:dump and drush sql:sync between environments are common steps in pipelines. sql:* commands are only listed in the roadmap, with no issue yet, and a sync between two sites is a remote operation by nature. How the community will solve remote management is still unclear, and it is probably a topic for another article.

Deployments still need Drush

Separate from remote management, the deployment pipeline itself has gaps at the command level. Deployments face a simpler gap: several commands they rely on are not in dr yet. As of October, 2026, only 4 of the 14 commands on the roadmap to stable (#3606055) are available in 11.4.0, and site:install only with limits. However, some others are already "Fixed" and can be expected in a following stable release. The table below covers the roadmap commands plus a few others mentioned in this or other issues:

Drush commanddr statusIssue
cache:rebuildShipped in 11.4.0 
core:cronShipped in 11.4.0 (system:cron) 
core:statusShipped in 11.4.0 (system:status) 
site:installShipped (install), limited. Install with any DB driver: Needs work#3594342, #3594320
user:loginFixed in dev branch (11.x)#3594332
pm:installFixed in dev branch (11.x)#3594334
pm:uninstallFixed in dev branch (main)#3594335
pm:listFixed in dev branch (main)#3594336
config:exportFixed in dev branch (main)#3594337
config:importNeeds review#3594338
config:statusActive (postponed on another issue)#3594339
updatedbActive#3594333
updatedb:statusActive#3594340
sql:drop, sql:queryListed in the roadmap, no issue yet 
watchdog:showListed in the roadmap, no issue yet 
deployMentioned as missing by longwave (#125), no issue yet 

With updatedb, config:import and deploy still missing, the practical approach is to keep Drush in deployment scripts for now and use dr alongside it. Not every Drush command will move to core either, since implementing every Drush command is listed as "Not in scope" in the initiative (#3582246). Drush stays for the rest, and contrib commands need porting, or plain Symfony discovery, to work with dr.

Other gaps are still open: there is no table formatting standard yet (#3611694), help for invokable commands does not show their usage (#3606744), commands in subdirectories with a period in the name are not discovered (#3594184), there is an 11.4.4 logger regression (#3612206) and user guide documentation is pending (#3625734). The roadmap targets the essential commands for Drupal 12.0.0, with a feature-complete set most likely in a minor release in 2027.

Conclusion

A CLI in core removes a decade-old dependency on an external tool for basic operations. This time the conditions are in place: Drush is ready to step aside, core committers are aligned, the old blockers have answers and the code has shipped.

The transition has started, and knowing it now avoids being wrong-footed, especially on remote operations. dr works locally and site aliases are not planned, so workflows built on aliases and sql:sync will likely need more than replacing drush with dr. Until then, Drush remains necessary for remote work and deployments. What lands next depends on usage and feedback.

We got here for a practical reason. We are adding dr support to our own modules, and that work is still in progress. Understanding what was going on meant digging through drupal.org issues with hundreds of comments, a change record, a porting guide, blog posts and ten years of history. We used AI to help collect and read all of it. After following that history, this looks like the attempt that will stick, and once the picture was in one place, we decided to share it.

Start porting your contrib modules to dr, explore it on real projects and share the urgent needs you find in the #cli-in-core Slack channel or the CLI in Core initiative, because Drupal moves forward with community help.

  • Jorge Tutor

    CIO
  • Ricardo Sanz

    CTO
Answers are generated automatically by AI and may not be accurate.