Drupal 8 Composer Best Practices

Collage of JoAnn Falletta by Mark Dellas

Whether you are familiar with Composer or not, using it to manage dependencies on a Drupal project entails its own unique set of best practices. In this article, we will start by getting a Composer-managed Drupal project set up, and highlight some common questions and issues you may have along the way.

Before we dive in, though, you may be asking yourself, “Why Composer? Can’t I just download Drupal and the modules I need without requiring another tool?” Yes you can, but you will quickly realize it’s not a simple task:

  1. Contributed modules or themes often depend on third-party libraries installed via Composer. Without using Composer for the project, you’ll need to manage these individually when downloading, which can be quite a chore.
  2. Some packages and modules only work with certain versions of PHP or Drupal. While Drupal core does help you identify these issues for modules and themes, it’s still a manual process that you’ll need to work through when choosing which versions to download.
  3. Some packages and modules conflict with other packages. You’ll need to read the composer.json files to find out which.
  4. When you upgrade a package or a version of PHP, you’ll need to do all the above over again.
  5. If you’re thinking you’ll use drush dl and friends, they’ve been removed in favor of Composer.

Dependency management is complicated, and it’s not going to get any easier. As Ryan Szrama put it , “if you’re not using a dependency manager [like Composer], then you’re the dependency manager, and you are unreliable.”

Where do I start?

To begin, it’s good to familiarize yourself with the fantastic documentation on Drupal.org . If you haven’t already, install Composer in your development environment. When getting started, it’s extremely important that your development environment is using the same version of PHP that the target production environment will use. Otherwise Composer may download packages that won’t work in production. (With larger development teams, using something like Vagrant or Docker for local development can help ensure compatibility between local development instances and the production stack, but that’s a separate topic).

To start with, you need to get Drupal core installed. If you are familiar with using starter-kits like Drupal Boilerplate in Drupal 7, there is a Composer template for Drupal projects called, well, drupal-project. It’s the recommended starting place for a Composer-based Drupal 8 installation. Before you create it—again—be sure you are using the production version of PHP. Once you’ve confirmed that, run:

$ composer create-project drupal-composer/drupal-project:8.x-dev \
example --stability dev --no-interaction

This will copy the drupal-project to the example directory, and download Drupal core and some handy packages. This is a good point to cd into the example directory, run git init , and to then create your initial commit for your project.

This also might be a good time to pop open the composer.json file that was created in there (which should look a lot like this). This file serves as your “recipe” for the project.

How do I download modules and themes?

If you are familiar with using drush dl in Drupal 7 to download contributed modules and themes, it can be a bit of a shift switching to a Composer workflow. There is a different syntax for selecting the version you’d like, and in some cases it’s best to not commit the actual downloaded files to your repository (more on that to follow).

For most instances, you can use composer require [vendor]/[packagename] to grab the package you’d like. Most Drupal modules and themes are hosted on a custom Composer repository, which drupal-project configures for us out of the box. That means we can use drupal as the vendor to download Drupal modules and themes that are maintained on drupal.org. If you aren’t using drupal-project, you may need to add the Drupal.org Composer repository yourself.

For example, if you were wanting to download the Devel module, you would run the following command:

$ composer require drupal/devel

For most cases, that would download the most recent stable version of the Devel module that is compatible with your version of Drupal and PHP. If you used drupal-project above, it would download the module to web/modules/contrib. If you want to change the download destination, you can alter the paths in your composer.json. Look for the installer-paths under extra and adjust to your liking.

Development Dependencies

The Devel module is a good example to use, as it brings up a few other things to note. For example, since the Devel module shouldn’t be used in a production environment, you may only want it to get downloaded on development environments. With Composer, you can achieve this by passing the --dev flag to the composer require command.

$ composer require --dev drupal/devel

This will ensure that the Devel module is available for your developers when they run composer install , but that it won’t ship with your production release (which should get built using composer install --no-dev ). This is just an example, and your project needs may differ, but the use of --dev to specify dependencies only needed during development is a recommended best practice.

As a side note, if you are committing your exported Drupal 8 configuration to code, you may want to use the Configuration Split module to ensure that the Devel module’s enabled status doesn’t get pushed to production by mistake in a release.

Nested Dependencies

Another thing to note is that the Devel module ships with its own composer.json file. Since we used Composer to download Devel, Composer will also read in Devel’s composer.json and download any dependencies that it may have. In this case (at the time of this writing), Devel doesn’t have any required dependencies, but if you were to use Composer to download, say Address module, it would pull in its additional dependencies as well, as Address module uses Composer to specify some additional dependencies .

Downloading specific versions

While it’s most often fine to omit declaring a specific version like we did above, there still may come a time when you need to narrow the range of possibilities a bit to get a package version that is compatible with your application.

Judging by the length of the Composer documentation on Versions and constraints, this is no small topic, and you should definitely read through that page to familiarize yourself with it. For most cases, you’ll want to use the caret constraint (e.g., composer require drupal/foo:^1.0 means the latest release in the 8.1 branch), but here’s some more details about versions and constraints:

  1. Read up on Semantic Versioning if you aren’t familiar with it. Basically, versions are in the x.y.z format, sometimes described as MAJOR.MINOR.PATCH , or BREAKING.FEATURE.FIX . Fixes increment the last number, new features increment the middle number, and refactors or rewrites that would break existing implementations increment the first number.
  2. Use a colon after the vendor/package name to specify a version or constraint, e.g. composer require drupal/foo:1.2.3 (which would require that exact version (1.2.3) of the foo package). If you’re used to drush syntax (8.x-1.2.3), the Composer syntax does not include the Drupal version (i.e., there's no 8.x).
  3. Don’t specify an exact version unless you have to. It’s better to use a constraint.
  4. The caret constraint (^): this will allow any new versions except BREAKING ones—in other words, the first number in the version cannot increase, but the others can. drupal/foo:^1.0 would allow anything greater than or equal to 1.0 but less than 2.0.x . If you need to specify a version, this is the recommended method.
  5. The tilde constraint (~): this is a bit more restrictive than the caret constraint. It means composer can download a higher version of the last digit specified only. For example, drupal/foo:~1.2 will allow anything greater than or equal to version 1.2 (i.e., 1.2.0, 1.3.0, 1.4.0,…,1.999.999), but it won’t allow that first 1 to increment to a 2.x release. Likewise