drupal/core-recommended
Drupal core-recommended is a Composer metapackage that pins Drupal core and its recommended dependencies to compatible, secure versions, simplifying upgrades and helping teams maintain a stable Drupal installation with tested component versions.
drupal/core-recommended is a Composer metapackage — it contains no PHP code. Its only job is to require Drupal core plus a curated set of transitive dependencies (Symfony components, Guzzle, Twig, composer/semver, etc.) at versions that the Drupal release was tested against. Think of it as a "known good" version manifest pinned to each core release.
Minimal setup. In a site project, require the metapackage instead of drupal/core:
composer require drupal/core-recommended:^10.2 --update-with-dependencies
If you started from drupal/recommended-project, this is already in your root composer.json — the standard layout is:
{
"require": {
"drupal/core-composer-scaffold": "^10.2",
"drupal/core-recommended": "^10.2"
},
"require-dev": {
"drupal/core-dev": "^10.2"
}
}
First place to look. After composer install, open vendor/drupal/core-recommended/composer.json. That file is the package — it lists every exact version pin for the current core release. When you wonder "why is symfony/console stuck at this version?", this file is the answer. Also run:
composer why drupal/core-recommended # confirms it's required by root
composer show drupal/core-recommended # shows the version you're on
First practical use case. Upgrading core safely. Instead of hand-editing dozens of constraints, you bump one metapackage and Composer resolves the rest of the tested stack with it:
composer update drupal/core-recommended --with-all-dependencies
drush updatedb && drush cache:rebuild
That single command is the primary day-to-day interaction most developers have with this package: pin, upgrade, verify.
Standard site upgrade workflow.
drupal/core-recommended (and drupal/core-dev if present) in composer.json or via composer require drupal/core-recommended:^10.3 --update-with-dependencies.composer update "drupal/core-*" --with-all-dependencies — this lets the metapackage pull its pinned transitive dependencies along with it.composer.lock diffs before deploying. Large Symfony/Guzzle bumps ride along with every core minor release.drush updb, drush cr, and your test suite. Database updates are core's job; regression from a bumped transitive dependency is this package's job.composer.lock. Always.Keeping a custom module or distribution aligned. If you maintain your own install profile or distribution, model your dependency section on drupal/recommended-project: require drupal/core-recommended at the same constraint as core, and let Composer resolve the rest. Don't require individual Symfony components at floating constraints — it will fight the metapackage.
Deliberate overrides for security patches. When a transitive dependency has a CVE and the pinned version is affected, add an explicit constraint to your root composer.json:
"require": {
"drupal/core-recommended": "^10.2",
"symfony/http-foundation": "^6.4.4"
}
Then run composer update symfony/http-foundation --with-all-dependencies. Composer will accept it only if the override is compatible with everything else in the graph; if it isn't, you get a clear conflict message. This is the intended escape hatch — the metapackage is a baseline, not a cage.
Separation of runtime and dev tooling. Keep drupal/core-recommended in require and drupal/core-dev in require-dev. Production builds use composer install --no-dev --optimize-autoloader; CI runs the same install then executes PHPUnit, PHPStan, and coding standards. The two packages are versioned in lockstep with core, so bump them together.
Pinning for CI reproducibility. Because the metapackage pins exact versions per core release, composer install on a committed lock file gives you the same dependency graph everywhere. That reproducibility is the main reason to prefer it over requiring drupal/core alone — with just drupal/core, transitive constraints drift between machines and CI.
Do not require drupal/core and drupal/core-recommended at different constraints. core-recommended already requires drupal/core; your root constraint on core is redundant at best and a conflict source at worst. Require only the metapackage.
Exact pins make "just one newer library" painful. If you need guzzlehttp/guzzle at a newer minor because a contrib module requires it, core-recommended may pin an older minor. Add the override to root require, then composer update --with-all-dependencies. If Composer still refuses, run:
composer why-not guzzlehttp/guzzle "^7.8"
composer prohibits guzzlehttp/guzzle 7.8.0
That tells you exactly which package is holding the version down.
A bare composer update can be slow and risky. Without arguments, Composer may try to update everything in the graph, including packages you did not intend to touch. Prefer composer update "drupal/core-*" --with-all-dependencies for core upgrades, and treat full updates as a scheduled, tested operation.
Patches don't apply here. Since the metapackage has no code, cweagans/composer-patches entries targeting drupal/core-recommended do nothing. Patch drupal/core or the specific underlying library instead.
Core security releases may update core-recommended itself. When Drupal ships a security release, the metapackage version bumps in lockstep and its pins move. Watch the release notes: occasionally a core release bumps a transitive dependency to fix a CVE, and the diff in composer.lock is larger than the version string suggests.
Read the lock file diff on every core bump. The metapackage is designed to move many transitive dependencies at once. A "minor" Drupal 10.2 → 10.3 upgrade can ship several Symfony minor bumps in the same lock diff. Run your test suite, and spot-check anything that changes behavior in HTTP stack, serialization, or translation.
Lock file is the source of truth for deployment. Never composer update in production. Deploy composer.json + composer.lock, run composer install --no-dev. This is doubly true with a metapackage, because the pins are what guarantee your environment matches the tested matrix.
You don't need this for module development. A contrib module's composer.json should never require drupal/core-recommended. Use it only for full site projects, distributions, or CI harnesses that stand up a complete Drupal site.
How can I help you explore Laravel packages today?