Using Composer With WordPress

Using Composer With WordPress Using Composer With WordPress
Leonardo Losoviz 2019-03-04T14:00:33+01:00

WordPress is getting modernized. The recent inclusion of
JavaScript-based Gutenberg as part of the core has added modern
capabilities for building sites on the frontend, and the
upcoming bump of PHP’s minimum version
, from the current
5.2.4 to 5.6 in April 2019 and 7.0 in December 2019, will make
available a myriad of new features to build powerful sites.

In my previous article on Smashing in which I identified

the PHP features newly available to WordPress
, I argued that
the time is ripe to make components the basic unit for building
functionalities in WordPress. On one side, Gutenberg already makes
the block (which is a high-level component) the basic unit to build
the webpage on the frontend; on the other side, by bumping up the
required minimum version of PHP, the WordPress backend has access
to the whole collection of PHP’s Object-Oriented Programming
features (such as classes and objects, interfaces, traits and
namespaces), which are all part of the toolset to think/code in

So, why components? What’s so great about
them? A “component” is not an implementation (such as a React
component), but instead, it’s a concept: It represents the act of
encapsulating properties inside objects, and grouping objects
together into a package which solves a specific problem. Components
can be implemented for both the frontend (like those coded through
JavaScript libraries such as React or Vue, or CSS component
libraries such as Bootstrap) and the backend.

We can use already-created components and customize them for our
projects, so we will boost our productivity by not having
to reinvent the wheel each single time
, and because of
their focus on solving a specific issue and being naturally
decoupled from the application, they can be tested and bug-fixed
very easily, thus making the application more maintainable in the
long term.

The concept of components can be employed for different uses, so
we need to make sure we are talking about the same use case. In a
previous article, I described how to
componentize a website
; the goal was to transform the webpage
into a series of components, wrapping each other from a single
topmost component all the way down to the most basic components (to
render the layout). In that case, the use case for the component is
for rendering — similar to a React component but coded in the
backend. In this article, though, the use case for components is
importing and managing functionality into the application.

Getting workflow just right ain’t an easy task. So are proper
estimates. Or alignment among different departments. That’s why
we’ve set up “this-is-how-I-work”-sessions
— with smart cookies sharing what works well for them. A part of
the Smashing Membership, of

Explore Smashing
Membership ↬

Smashing TV, with live sessions for professional designers and developers.

Introduction To Composer And Packagist

To import and manage own and third-party components into our PHP
projects, we can rely on the PHP-dependency manager Composer which by default retrieves
packages from the PHP package repository Packagist (where a package is
essentially a directory containing PHP code). With their ease of
use and exceptional features, Composer + Packagist have become key
tools for establishing the foundations of PHP-based

Composer allows to declare the libraries the project depends on
and it will manage (install/update) them. It works
: libraries depended-upon by dependencies will
be imported to the project and managed too. Composer has a
mechanism to resolve conflicts: If two different libraries depend
on a different version of a same library, Composer will try to find
a version that is compatible with both requirements, or raise an
error if not possible.

To use Composer, the project
simply needs a composer.json file
in its root folder. This file
defines the dependencies of the project (each for a specific
version constraint based on semantic
) and may contain other metadata as well. For
instance, the following composer.json file makes a project require
nesbot/carbon, a library providing an extension for DateTime, for
the latest patch of its version 2.12:

    "require": {
        "nesbot/carbon": "2.12.*"

We can edit this file manually, or it can be created/updated
through commands. For the case above, we simply open a terminal
window, head to the project’s root directory, and type:

composer require "nesbot/carbon"

This command will search for the required library in Packagist
(which is found here) and add
its latest version as a dependency on the existing composer.json
file. (If this file doesn’t yet exist, it will first create it.)
Then, we can import the dependencies into the project, which are by
default added under the vendor/ folder, by simply executing:

composer install

Whenever a dependency is updated, for instance nesbot/carbon
released version 2.12.1 and the currently installed one is 2.12.0,
then Composer will take care of importing the corresponding library
by executing:

composer update

If we are using Git, we only have to specify the vendor/ folder
on the .gitignore file to not commit the project dependencies under
version control, making it a breeze to keep our project’s code
thoroughly decoupled from external libraries.

Composer offers plenty of additional features, which are
properly described in the documentation.
However, already in its most basic use, Composer gives developers
unlimited power for managing the project’s dependencies.

Introduction To WPackagist

Similar to Packagist, WPackagist is a PHP package
repository. However, it comes with one particularity: It contains
all the themes and plugins hosted on the WordPress plugin and theme directories, making them
available to be managed through Composer.

To use WPackagist, our composer.json file must include the
following information:


Then, any theme and plugin can be imported to the project by
using “wpackagist-theme” and “wpackagist-plugin” respectively as
the vendor name, and the slug of the theme or plugin under the
WordPress directory (such as “akismet” in
as the package name. Because themes do not have a trunk version,
then the theme’s version constraint is recommended to be

    "require": {

Packages available in WPackagist have been given the type
“wordpress-plugin” or “wordpress-theme”. As a consequence,
after running composer update, instead of installing the
corresponding themes and plugins under the default folder vendor/,
these will be installed where WordPress expects them: under folders
wp-content/themes/ and wp-content/plugins/ respectively.

Possibilities And Limitations Of Using WordPress And Composer

So far, so good: Composer makes it a breeze to manage a PHP
project’s dependencies. However, WordPress’ core hasn’t
adopted it as its dependency management tool of choice, primarily
because WordPress
is a legacy application that was never designed to be used with
, and the community can’t agree if WordPress should be
considered the site or a site’s dependency
, and integrating
these approaches requires hacks.

In this concern, WordPress is outperformed by newer frameworks
which could incorporate Composer as part of their architecture. For
instance, Laravel underwent a
major rewriting in 2013 to establish Composer
as an application-level package manager
. As a consequence,
WordPress’ core still does not include the composer.json file
required to manage WordPress as a Composer dependency.

Knowing that WordPress can’t be natively managed through
Composer, let’s explore the ways such support can be added, and what
roadblocks we encounter in each case.

There are
three basic ways in which WordPress and Composer can work

  1. Manage dependencies when developing a theme or a plugin;
  2. Manage themes and plugins on a site;
  3. Manage the site completely (including its themes, plugins and
    WordPress’ core).

And there are two basic situations concerning who will have
access to the software (a theme or plugin, or the site):

  1. The developer can have absolute control of how the software
    will be updated, e.g. by managing the site for the client, or
    providing training on how to do it;
  2. The developer doesn’t have absolute control of the admin user
    experience, e.g. by releasing themes or plugins through the
    WordPress directory, which will be used by an unknown party.

From the combination of these variables, we will have more or
less freedom in how deep we can integrate WordPress and Composer

From a philosophical aspect concerning the objective and target
group of each tool, while Composer empowers developers, WordPress
focuses primarily on the needs of the end users first, and only
then on the needs of the developers. This situation is not
self-contradictory: For instance, a developer can create and launch
the website using Composer, and then hand the site over to the end
user who (from that moment on) will use the standard
procedures for installing themes and plugins
— bypassing
Composer. However, then the site and its composer.json file fall
out of sync, and the project can’t be managed reliably through
Composer any longer: Manually deleting all plugins from the
wp-content/plugins/ folder and executing composer update will not
re-download those plugins added by the end user.

The alternative to keeping the project in sync would be to ask
the user to install themes and plugins through Composer. However,
this approach goes against WordPress’
: Asking the end user to execute a command such as
composer install to install the dependencies from a theme or plugin
adds friction, and
WordPress can’t expect every user to be able to execute this
, as simple as it may be. So this approach can’t be the
default; instead, it can be used only if we have absolute control
of the user experience under wp-admin/, such as when building a
site for our own client and providing training on how to update the

The default approach, which handles the case when the party
using the software is unknown, is to release themes and plugins
with all of their dependencies bundled in. This implies that the
dependencies must also be uploaded to WordPress’ plugin and theme subversion
repositories, defeating the purpose of Composer. Following this
approach, developers are still able to use Composer for
development, however, not for releasing the software.

This approach is not failsafe either: If two different plugins
bundle different versions of a same library which are incompatible
with each other, and these two plugins are installed on the same
site, it could cause
the site to malfunction
. A solution to this issue is to modify
the dependencies’ namespace to some custom namespace, which
ensures that different versions of the same library, by having
different namespaces, are treated as different libraries. This can
be achieved through a
custom script
or through Mozart, a library which
composes all dependencies as a package inside a WordPress

For managing the site completely, Composer must install
WordPress under a subdirectory as to be able to install and update
WordPress’ core without affecting other libraries, hence the
setup must consider WordPress as a site’s dependency and not the
site itself. (Composer doesn’t take a stance: This decision is
for the practical purpose of being able to use the tool; from a
theoretical perspective, we can still consider WordPress to be the
site.) Because WordPress can be installed
in a subdirectory
, this doesn’t represent a technical issue.
However, WordPress is by default installed on the root folder, and
installing it in a subdirectory involves a conscious decision taken
by the user.

To make it easier to completely manage WordPress with Composer,
several projects have taken the stance of installing WordPress in a
subfolder and providing an opinionated composer.json file with a
setup that works well: core contributor John P. Bloch
provides a mirror of
WordPress’ core
, and Roots
provides a WordPress boilerplate called Bedrock. I will describe how to use
each of these two projects in the sections below.

Managing The Whole WordPress Site Through John P. Bloch’s Mirror
Of WordPress Core

I have followed Andrey “Rarst” Savchenko’s recipe for
the whole site’s Composer package
, which makes use of John P.
Bloch’s mirror of WordPress’ core. Following, I will reproduce
his method, adding some extra information and mentioning the
gotchas I found along the way.

First, create a composer.json file with the following content in
the root folder of your project:

    "type": "project",
    "config": {
        "vendor-dir": "content/vendor"
    "extra": {
        "wordpress-install-dir": "wp"
    "require": {
        "johnpbloch/wordpress": ">=5.1"

Through this configuration, Composer will install WordPress 5.1
under folder “wp”, and dependencies will be installed under folder
“content/vendor”. Then head to the project’s root folder in
terminal and execute the following command for Composer to do its
magic and install all dependencies, including WordPress:

composer install --prefer-dist

Let’s next add a couple of plugins and the theme, for which we
must also add WPackagist as a repository, and let’s configure
these to be installed under “content/plugins” and “content/themes”
respectively. Because these are not the default locations expected
by WordPress, we will later on need to tell WordPress where to find
them through constant WP_CONTENT_DIR.

Note: WordPress’ core includes by default a
few themes and plugins under folders “wp/wp-content/themes” and
“wp/wp-content/plugins”, however, these will not be accessed.

Add the following content to composer.json, in addition to the
previous one:

    "repositories": [
            "type": "composer",
            "url" : ""
    "require": {
        "wpackagist-plugin/wp-super-cache": "1.6.*",
        "wpackagist-plugin/bbpress": "2.5.*",
        "wpackagist-theme/twentynineteen": "*"
    "extra": {
        "installer-paths": {
            "content/plugins/{$name}/": ["type:wordpress-plugin"],
            "content/themes/{$name}/": ["type:wordpress-theme"]

And then execute in terminal:

composer update --prefer-dist

Hallelujah! The theme and plugins have been installed! Since all
dependencies are distributed across folders wp, content/vendors,
content/plugins and content/themes, we can easily ignore these when
committing our project under version control through Git. For this,
create a .gitignore file with this content:


Note: We could also directly ignore folder
content/, which will already ignore all media files under
content/uploads/ and files generated by plugins, which most likely
must not go under version control.

There are a few things left to do before we can access the site.
First, duplicate the wp/wp-config-sample.php file into
wp-config.php (and add a line with wp-config.php to the .gitignore
file to avoid committing it, since this file contains environment
information), and edit it with the usual information required by
WordPress (database information and secret keys and salts). Then,
add the following lines at the top of wp-config.php, which will
load Composer’s autoloader and will set constant WP_CONTENT_DIR
to folder content/:

// Load Composer’s autoloader
require_once (__DIR__.'/content/vendor/autoload.php');
// Move the location of the content dir
define('WP_CONTENT_DIR', dirname(__FILE__).'/content');

By default, WordPress sets constant WP_CONSTANT_URL with value
get_option(‘siteurl’).’/wp-content’. Because we have changed the
content directory from the default “wp-content” to “content”, we
must also set the new value for WP_CONSTANT_URL. To do this, we
can’t reference function get_option since it hasn’t been
defined yet, so we must either hardcode the domain or, possibly
better, we can retrieve it from $_SERVER like this:

$s = empty($_SERVER["HTTPS"]) ? '' : ($_SERVER["HTTPS"] == "on") ? "s" : "";
$sp = strtolower($_SERVER["SERVER_PROTOCOL"]);
$protocol = substr($sp, 0, strpos($sp, "/")) . $s;
$port = ($_SERVER["SERVER_PORT"] == "80") ? "" : (":".$_SERVER["SERVER_PORT"]);
define('WP_CONTENT_URL', $protocol."://".$_SERVER[’SERVER_NAME'].$port.'/content');

We can now access the site on the browser under,
and proceed to install WordPress. Once the installation is
complete, we log into the Dashboard and activate the theme and

Finally, because WordPress was installed under subdirectory wp,
the URL will contain path “/wp” when accessing the site.
Let’s remove that (not for the admin side though, which by being
accessed under /wp/wp-admin/ adds an extra level of security to the

The documentation proposes two methods to do this:
URL change. I followed both of them, and found the
without URL change a bit unsatisfying because it requires
specifying the domain in the .htaccess file, thus mixing
application code and configuration information together. Hence,
I’ll describe the method with URL change.

First, head to “General Settings” which you’ll find under and remove the “/wp”
bit from the “Site Address (URL)” value and save. After doing
so, the site will be momentarily broken: browsing the homepage will
list the contents of the directory, and browsing a blog post will
return a 404. However, don’t panic, this will be fixed in the
next step.

Next, we copy the index.php file to the root folder, and edit
this new file, adding “wp/” to the path of the required file,
like this:

/** Loads the WordPress Environment and Template */
require( dirname( __FILE__ ) . '/wp/wp-blog-header.php' );

We are done! We can now access our site in the browser under

WordPress site WordPress site successfully
installed through Composer (Large

Even though it has downloaded the whole WordPress core codebase
and several libraries, our project itself involves only six
from which only five need to be committed to

  1. .gitignore
  2. composer.json
  3. composer.lock
    This file is generated automatically by Composer, containing the
    versions of all installed dependencies.
  4. index.php
    This file is created manually.
  5. .htaccess
    This file is automatically created by WordPress, so we could avoid
    committing it, however, we may soon customize it for the
    application, in which case it requires committing.

The remaining sixth file is wp-config.php which must not
be committed
since it contains environment

Not bad!

The process went pretty..