A Modern Approach to WordPress

/ Reading time: 13 minutes Fork on Github
  1. Introduction
  2. Essential Plugins
  3. Composer and WordPress Packagist
  4. Getting WordPress running
  5. WP CLI and Environment Variables

Love it or hate it, you cannot ignore WordPress. It’s estimated that around 27% of all websites run on it which is driven by its price (free), updates (often), plugins (anything you can imagine), themes (plentiful), and developability (you could build practically anything on top of it).

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:

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 the Twig template engine for your HTML and keeping your PHP separate. 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. 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.

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.

  "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}/": ["wpackagist-plugin/advanced-custom-fields", "wpackagist-plugin/timber-library"],
      "wp-content/plugins/{$name}/": ["type: wordpress-plugin"]

This composer file states a few things:

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. You’ll notice that we specify the exact packages that we want to place in this folder. Any plugins not named specifically will go into the standard plugins folder to be enabled in admin later.

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:

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: 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):

# 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...
LOGGED_IN_KEY='bu9z)#=S&<r(1+,.mV!Cd&d>&[Mrdx20IPJCrbJb4^+1^Pd6;W09FeIGs<v FJR]'
AUTH_SALT='G&5j*-Ot,}N~b}2_(zq-Jqc&-fVZ=2da2Ga5%-XWUr>8|4EXAsL K>{JQ:z90t$7'
SECURE_AUTH_SALT='ej68*YwNt76%:R*1OS9Tnb%La5 [I?!- g:0V#A[~nD8`[wau:CxZ.6EanQ%vb93'
LOGGED_IN_SALT='PTK%#-Fe|_SJc6>n~IqTsTPqi?#hzQx(tU<x<ic@qT2/59[Mmf^n0Sk6k-m- hTN'
NONCE_SALT='Qo:(YR&Du< )d[b|JzC{uj1z|9p?cWmCbwKX`)!kb~PD {CX(B:Z<hS4O:L6_2eu'

IMPORTANT: Do not use the Salts and Keys above.

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:

An example starter project using everything covered above can be found on GitHub. Fork it, hack it, and if you feel it can be improved submit a pull request.