WordPress should be treated as a dependency like any other. With Composer and a little time it's not too hard to get there.


If using WordPress for a large, complex, and bespoke site you may not want to use it as it comes out of the box. This article covers the approach I like to take, and starts with an empty folder.

This guide assumes a few things:

  • You have a basic understanding of WordPress.
  • You have a database running that you can connect to.
  • You have a development environment where you can run PHP.
  • You're comfortable with the command line.
  • You have Composer installed and know the basics.

Essential plugins

In keeping with the theme of this post, these two plugins are geared towards development. There are many plugins for SEO and security that could also be considered essential, but that's for another time.


Theme development with WordPress is powerful and extremely well documented but when building something particularly large and complex you will end up with a PHP/HTML soup that is hard to work with. You will also relinquish some control over the exact HTML structure you end up with as well as class names and IDs, which could force you into writing compromised CSS.

Timber helps to solve this by using Twig templates for your HTML and keeping your PHP separated. Theme files are split in to a view/controller pattern keeping your code cleaner, easier to read and more portable between stacks should that ever need to happen.

Advanced Custom Fields

Being able to add fields to posts, pages and your custom post types is essential if you need to display data beyond a title and post body. Advanced Custom Fields is generally considered to be the best plugin to handle this, with the free version being adequate in many cases.

The Pro version is so reasonably priced it's worth buying just in case you need any of the extra features it provides. It also means that Elliot Condon can continue to develop it. There are a couple of features included with Pro that I find particularly valuable:

  • Local JSON saves all of your custom fields to a JSON object within your theme (fields are otherwise only saved to the database) making them distributable. However, when working in a team where multiple developers are adding and amending fields, things can very easily get out of sync.
  • Options Page allows you to create fields that can be used anywhere in your site, not just on a particular post, which is useful for things like footer information, but can also be used when making archive pages for custom post types.

If using the Pro version, take a look at ACF Pro Installer.

Composer and WordPress Packagist

The WordPress plugin directory does not support Composer as standard, but fortunately WordPress Packagist mirrors the directory and adds support. We can grab all the packages, plugins and even WordPress itself directly using Composer.

Create composer.json at the root of your new project folder and add the below content. We'll go over the relevant parts in a moment.

NOTE: This article may be reasonably old by the time you're reading this, so check the versions defined below. They may be very out of date.

  "repositories": [
      "type": "composer",
      "url": "https://wpackagist.org"
  "require": {
    "php": ">=5.4",
    "composer/installers": "~1.0",
    "vlucas/phpdotenv": "^2.0",
    "johnpbloch/wordpress": "4.7._",
    "wpackagist-plugin/advanced-custom-fields": "4.4._",
    "wpackagist-plugin/timber-library": "1.3.*"
  "extra": {
    "wordpress-install-dir": "wp",
    "installer-paths": {
      "wp-content/mu-plugins/{$name}/": ["type:wordpress-plugin"]

This composer file states a few things:

  • Add WordPress Packagist as a composer repository we can use.
  • List all the packages that are required in this project.
  • Define where some of these packages need to be installed, which is important for WordPress and the plugins.

As well as our two essential plugins that we are fetching from Packagist we are getting WordPress itself from GitHub and getting a package that extends WP-CLI. We'll go over this one a little later.

Finally we specify where some of these plugins need to be installed. By default, Composer packages go in a vendor folder but we want to place WordPress in a folder named wp and our plugins in wp-content/mu-plugins.

Must-use plugins

Plugins in the mu-plugins folder are automatically enabled at all times and do not appear in admin. They cannot be disabled by anyone without being physically removed. One disadvantage is that these plugins need to be required in code, so create index.php in this folder and add:

require WPMU_PLUGIN_DIR.'/advanced-custom-fields-pro/acf.php';
require WPMU_PLUGIN_DIR.'/timber-library/timber.php';

Installing packages

To install everything we need, run $ composer install. This will fetch everything into their defined folders and create a new file - composer.lock - that needs to be added to source control along with composer.json to keep track of dependencies.

Getting WordPress running

As WordPress is installed in a subfolder, we need to copy a couple of files from wp to the project root:

  • wp-config-sample.php, which must be renamed to wp-config.php.
  • index.php

If it doesn't yet exist, create .htaccess too.

Open index.php and edit the path to wp-blog-header.php to take into account the WordPress install location:

// Change this
require( dirname( __FILE__ ) . '/wp-blog-header.php' );

// ...to this
require( dirname( __FILE__ ) . '/wp/wp-blog-header.php' );

Open wp-config.php and add the following settings to ensure WordPress looks in the correct folders for plugins and themes:

NOTE: You could rename wp-content at this point if you wanted.

define('WP_CONTENT_DIR', __DIR__ . '/wp-content');
define('WP_CONTENT_URL', 'http://' . $_SERVER['SERVER_NAME'] . '/wp-content');

To use the Timber starter theme, copy it from mu-plugins/timber-library to wp-content/themes. As this will be the only theme available you might want to add to your config to set this as the default theme on install: define('WP_DEFAULT_THEME', 'timber-starter-theme');.

To complete the next steps, update your wp-config.php with your database credentials. We'll revisit this later and use environment variables.

Visit your site in your development environment and follow the steps to install WordPress, then log in to admin. We are now running WordPress with a new folder structure and our essential plugins running with all these dependencies managed with Composer.

WP CLI and Environment Variables

WP-CLI is an extremely powerful tool for managing WordPress environments from the command line. If you need to take care of multiple installs, have multisite network or want to automate particular tasks it's going to save a huge amount of time. A full list of features and packages can be found here and steps to install can be found here.

I'm only going to cover one package in this guide: Dotenv Command. We have already installed this package via composer, so let's start using it.

We added database credentials to wp-config.php earlier, and still need to add keys and salts for security. Keeping this information here isn't ideal as this config file needs to be stored in source control (potentially revealing sensitive data) and deployed to all environments (which require different configurations). This is a headache to manage, especially since this entire approach requires a highly customised config.

Create a new file at the project root named .env.example with the below contents (you can add anything extra you feel is appropriate for your project, like API keys):

# Standard WordPress credentials.

# Salts and keys generated below...

This file acts as a template for the actual file and is used by the CLI to prompt you for specific values. Where localhost is already defined for DB_HOST, the CLI will take this as the default value unless you specify something else.

Run this command: $ wp dotenv init --template=.env.example --interactive --with-salts and add the required values as prompted. Once you're done, a new .env file will be created and look something like this:

# Standard WordPress credentials.

# Salts and keys generated below...

We now need to update wp-config.php to use the environment variables. First, add this to the top of your file to load the Dotenv package from composer:

require __DIR__.'/vendor/autoload.php';
$dotenv = new Dotenv\Dotenv(__DIR__);

Next, update the environment variables to use the values in .env. For example:

// Change this...
define('DB_NAME', 'wp_test');

// ...to this.
define('DB_NAME', getenv('DB_NAME'));

Your config will now be stored in the .env text file and will need to be generated in each environment. Your config will not need to change any further.

IMPORTANT: Ensure you deny access to both .env and .env.example on your web server.

In summary

We now have a starting point for WordPress development. This may feel like a lot of work to set up but there are some serious benefits:

  • All third party code is excluded from source control. At this point, you should consider WordPress to be a dependency of your project.
  • With Composer and WordPress Packagist, the correct versions of packages, plugins and WordPress itself will be obtained after running one install command.
  • The timber starter theme and our essential plugins are automatically enabled and ready to go.
  • We have all environment variables separated from our config and a means to generate a .env file on all environments.

An example starter project using everything covered above can be found on GitHub.