A modern Composer workflow with WordPress

Updated on 27th August with different methods for getting WordPress packages

This is a guide on how to incorporate a modern Composer workflow into your WordPress builds. This aimed at people who run WordPress websites as a service – typically bespoke theme developers who want to get a bit more out of WordPress as a tool.

This article prerequisites an understanding of Composer; Composer is a great PHP package manager so if you haven’t used it before give it a look. I won’t go into the specifics of how to use it or set it up as there are plenty of great guides on how to do that.

Composer by default installs it’s packages in `/vendor/` and adds the contents to the PHP autoloader. When we want to install WordPress, that’s not so much what we want. This article should be useful for anyone that wants to install PHP packages that don’t necessarily follow the composer format.


fancyguy has done a great job creating a composer plugin that installs specific composer packages into a predefined webroot, and then install packages of a defined type into specific locations – say for example WordPress plugins.

    "repositories": [
            "type": "composer",
            "url": "http://wpackagist.org"
        }, {
            "type": "composer",
            "url": "http://carbon:supersecretpassword@packages.carboncreative.net/"
    "require": {
        "php": ">=5.3.0",
        "carboncreative/wordpress": "~3.9",
        "fancyguy/webroot-installer": "~1.1",
        "wpackagist/advanced-custom-fields": "*",
        "wpackagist/akismet": "*",
    "extra": {
        "webroot-dir": "web/wordpress",
        "webroot-package": "carboncreative/wordpress",
        "installer-paths": {
            "web/wp-content/plugins/{$name}/": ["type:wordpress-plugin"],
            "web/wp-content/themes/{$name}/": ["type:wordpress-theme"]


In the extra section of our `/composer.json` file, `webroot-installer` allows us to install WordPress in a “webroot”. In actual fact, our webroot is going to be `/web`, but composer doesn’t need to know this – we want WordPress to be installed in `/web/wordpress`.

We can also define types of composer package that are installed into specific places. In this example we take every package with the type `wordpress-plugin`, and install them into `/web/wp-content/plugins`.

This means that if the only code being put into the plugins directory is from composer, we can gitignore the entire directory. And we can also gitignore WordPress itself. We can build ourselves into a situation where we commit nothing third party into our repository, other than the auto-generated code composer outputs to composer.lock.

WordPress core and private/paid plugins

Unfortunately there is no official WordPress package in Packagist yet, but there are efforts to get a composer.json into the codebase. There are a few options for this:

If we host our own, it’s easy with the aid of Satis. With Satis, we define our packages as if we were setting up custom repositories in a `composer.json`, and then Satis creates a fully fledged packages.json structure that we can point a `composer.json` to. It needs hosting somewhere, and you may want to pop HTTP Basic Auth on the top of it if you want to store private or paid plugins here too. This is what I do for including paid for plugins such as Gravity Forms or ACF-Repeater. Another option for a Satis repository for plugins is SatisPress, which is a very simple WordPress install, which allows for the self-updating features of private plugins.

The other alternative to setting up Satis is to point to a very simple raw `packages.json` maintained by yourself. At Carbon we have one of these on GitHub, however I wouldn’t recommend you use that one as there’s no guarantee it’ll be updated.

WordPress.org plugins

Another great tool in the arsenal to set this up is WordPress Packagist. WordPress Packagist is a Composer repository which mirrors `plugins.svn.wordpress.org` and `themes.svn.wordpress.org`. This means that every plugin and theme hosted on WordPress.org is available as a composer package. Take the slug of a plugin from the URL of it’s page (e.g. `advanced-custom-fields` from `https://wordpress.org/plugins/advanced-custom-fields/`) and use a * as the version number in our composer.json to get the latest stable version.

Project set up

This will be our directory structure when we're finished
This will be our directory structure when we’re finished

Now we can treat WordPress’s internal code like a black box, but now we need to communicate with it to instantiate WordPress.

This starts with our frontend controller that delegates requests to the WordPress code. This is exactly the same as WordPress’s frontend controller:

 * Application bootstrapper. Edit this file to route requests through WordPress
 * or another means as you see fit

define('WP_USE_THEMES', true);
require(__DIR__ . '/wordpress/wp-blog-header.php');

Note, we have full control over this; if we want to write our own PHP application next to WordPress, we can implement a router inside this file that only delegates certain requests to WordPress.

Thankfully, this sort of setup is not uncommon and WordPress knows this. By default, `wp-load.php` looks in the directory above itself if it cannot find a `wp-config.php` in the same directory. This means that we can place our `wp-config.php` outside `/web/wordpress`, and have our install point to a separate `wp-content/` directory.

Take your normal `wp-config.php` file and replace the following defines:

/** Absolute path to the WordPress directory. */
if ( !defined('ABSPATH') )
    define('ABSPATH', __DIR__ . '/wordpress/');

define('WP_HOME', 'http://' . $_SERVER['HTTP_HOST']);
define('WP_SITEURL', WP_HOME . '/wordpress');

define('WP_CONTENT_DIR', realpath(__DIR__ . '/wp-content/'));
define('WP_CONTENT_URL', WP_HOME . '/wp-content');

This will let WordPress know where to find stuff in our new directory structure.

One final file you may need to set up is a `wp-load.php` file. Sometimes a plugin or theme may rely on the fact that it can include `wp-load.php` by saying require __DIR__ . ‘/../../../wp-load.php’; , so you may need to set up something to bounce the requests back to `/web/wordpress/wp-load.php`, like so:

 * Bounce user to WordPress WP-load, so theme files can find it relative to themselves.
require_once __DIR__ . '/wordpress/wp-load.php';

So that’s all there is to it. It should all function as a completely normal WordPress install, with the only caveat being that the admin is now loaded from `http://yoursite.com/wordpress/wp-admin/`. It’s useful to set up a 301 redirect from `/wp-admin/` to `/wordpress/wp-admin/` if any of your clients/users are quite used to WordPress.

Next I’ll write about how to incorporate PSR standard autoloading into your WordPress builds, and what advantages that can bring.

A quick thanks to Antti-Jussi Kovalainen who helped me figure out the loading of WordPress as a blackbox in his article WordPress as a submodule, and whose wp-config.php settings you’re seeing here.






6 responses to “A modern Composer workflow with WordPress”

  1. Ryan Duff Avatar

    I’m not totally up to speed on Composer, but you said

    Unfortunately there is no WordPress package in Packagist yet

    But this exists… https://packagist.org/packages/johnpbloch/wordpress

    Maintained by John Bloch and synced from upstream every 15 min. Maybe not “official” but by the numbers it’s been adopted quite well.

    1. Tom Moitié Avatar

      Of course. I should have written that as “official WordPress package”. I’ll include John Bloch’s repository in the article. I still have the same reservations as I have with other’s pointing to our `packages.json` … That said, the exact same can be said for WPackagist!

  2. […] A modern Composer workflow with WordPress – Tom Moitié […]

  3. […] Updated on 27th August with different methods for getting WordPress packagesThis is a guide on how to incorporate a modern Composer workflow into your WordPress builds. This aimed at people who run WordPress websites as a service – typically bespoke theme developers who want to get a bit more Read more… […]

  4. Ramon Fincken Avatar

    In addition, you might like satispress when using premium plugins (commercial plugins):

    1. Tom Moitié Avatar

      Yes! Used that a couple of years ago at a previous job. Very useful, thanks.

Leave a Reply

Your email address will not be published. Required fields are marked *