WordPress + Composer + GIT

Matías Halles
14 min readAug 11, 2016


A tutorial for a tearless and fearless journey to WP nirvana.

This tutorial comes out of a small presentation done at The Auckland WordPress users group. Here are the slides. Lot’s of emoji. Thanks so much for receiving me :)

The usual WordPress workflow includes using code editing in a local environment, then using an FTP client (like Cyberduck) to upload your files and maybe change content or configurations through your admin panel on the site online. This works great (not so great) when working by yourself in a project.

“I’m a strong independent developer who don’t need no team.” — No one, ever.

So what happens if you want to work with someone else on a WP project? Same thing, code, upload through FTP, go into admin maybe, refresh browser. Unless your team mate just added some code in the same file you were editing, or changed a function that you were relying on, stepping on your code and leaving you with a missing piece of code. You’ll probably refresh and see some weird error going on in the site. Hopefully not on your already live client’s website. Hopefully not a php fatal error. Fixing the site while getting calls from your client = nope 😨

For those who know what git, composer, WordPress Packagist, ssh, ftp, blah, bleh, derp and hurr are, just jump onto the tutorial :)

Git for Version Control

Git allows you to track historically the changes in your code, being this your themes, your plugins or anything that has plain text. Git can track binary files, but it is not very good at it. You will have problems at some point and getting rid of these problems is not easy. I advice you not to unless they are small files that can’t be sourced in some other way.

Also one of Git’s objectives is to allow for distributed teamwork. It has tools that allow every dev worked separately and freely, without interrupting each other, and then merging changes in an orderly manner.

Advantages of using Git

  • Work with a team
  • F*** **** ** and revert fearlessly, even when not working in a team. Every step you’ve taken has been saved and can be reverted to.
  • Distribute and share your workload with other people easily: Need help to finish up something? Call your buddy and have her jump into the project in a breeze.

Composer + WPackagist

Composer is a dependencies managing tool for PHP. A dependency manager is a tool that will let you configure what your software depends on to work and make these dependencies available for you with no hassle. Think of it as a cook assistant: For bread you need 1 Kg of flour, 3 cups of water, 1 cup of milk, 100 gr of butter, 1 teaspoon of salt. Make your assistant get the stuff and have it ready for you to focus on cooking and not going grocery shopping. You can install composer directly into a project but I recommend you to install it system wide.

WordPress Packagist is a repositories site that automatically packages the plugins and themes available in WordPress site in the format required for working with composer. This is your groceries store.


  • Control used versions of WordPress, plugins and themes, preventing incompatibilities.
  • Deploy as if you were Usain Bolt.
  • Upgrade programatically

SSH and use the speed you deserve.

FTP man. It is so painfully, ridiculously, sloth, slow. Every. Damn. Time.

There’s a bunch of technical reasons why FTP uploading is slow, which don’t really matter. What matters is that login in to your server through SSH will let you stop uploading your files and start downloading them onto the server, at server speeds and directly from the original sources, being that WordPress servers, WordPress Packagist and others. In the tutorial below, you’ll be able to experience the speed difference.


  • Speed uploads, because no uploads.
  • Speed recovery because of version control and direct control in the server.
  • Feel fabulously nerdish and sexy on that Terminal 🤓 😏

Overall Advantages

  • No more code smashing
  • Fast and stable deployment any environment: dev, test or production
  • Reduced time loss
  • Opens doors for real automation: compile, minify, prevent cache issues.


  • Learning Curve (not so much)
  • Strict-ish development process
  • Making non devs a bit unhappy

The Tutorial

This tutorial will show you the basics on how to make all these tools work to your benefit. I will be assuming you already have all of these tools set up and know a bit of they are about:

  • SourceTree: (git graphical client for OS X and Windows. Comes with git embedded). I’m sure you linux guys can figure out what client to use. I haven’t used linux on desktop for a long time sorry :p
  • Github: Get an account.
  • A code editor: Atom.io is my current choice.
  • Composer: Instructions for all operating systems.

If you want to run your WordPress install, you should have your server stack running. This tutorial will not focus on running WordPress but on the development process.

All set? Ok then :)

Create 2 new repositories and clone them into your computer.

It doesn’t really matter what names you give them. Just make them clear that one is a the site and another is for the theme. The tutorial will produce two repositories like these:

To create a new repository in github, click on the + sign on the top right corner of the interface. A dropdown will drop, choose “repository”. For the sake of simplicity, choose to add a README file and a license. Do the same for the theme repository:

Now, time to clone your site repository locally. Go into the site repository and find the “clone or download” button. A drop down will pop with a URL. Copy that address to use in SourceTree afterwards.

Now, with SourceTree open, clone the repository into a directory. It really doesn’t matter what “Name” you choose for the purpose of the tutorial.

SourceTree will open the project in a new window. Switch to the history tab on the left column to see the project’s history, which will have only one commit at this point.

Currently there’s just a README.md file and a LICENSE file. We’ll first add a .gitignore file (tasked to configure what to ignore in a repository) and then a composer.json file which will have our initial composer configuration.

Create a file called .gitignore on the base of the project with your favorite code editor (remember it starts with a .) and add these lines:


This four lines will prevent git to track and nag about files installed under /content/ and /wp/ (which is where our plugins, themes and WordPress core will end up, as well as our wp-config.php file which should never be tracked. .DS_Store is there just for mac users. Annoying little pricks, those files. Next step, go to SourceTree. You’ll see you have a new point in the history with the name “Uncommited Changes”. Check the empty checkbox at the left of the file called .gitignore, press “Commit” on the toolbar. An input box will show up underneath. Fill it up with a comment that describes what you just did so you have an idea of what went on during that change. Could be just “add gitignore” and the press “commit” on the lower right corner. For those unfamiliar to git, it is a very good practice to be clear on what happened in that point on history. Your repository in Sourcetree should look like this:

Now let’s do the same creating a file called composer.json. This will have all of our configuration for dependencies and other things like, where to put wordpress related modules and it’s core. The contents of the file should be:

"name": "halles/wp-composer-site",
"description": "Basic template for creating a composer + wpackagist controlled site",
"authors": [
"name": "Matias Halles",
"email": "matias.halles@gmail.com"
"require": {
"wordpress": "4.5.*",
"wpackagist-plugin/w3-total-cache": "0.9.2",
"wpackagist-plugin/google-analytics-for-wordpress": "5.5.2"
"require-dev": {
"wpackagist-plugin/debug-bar": "0.8.2",
"wpackagist-plugin/debug-bar-console": "0.3"
"type" : "package",
"package" : {
"name" : "wordpress",
"type" : "webroot",
"version" : "4.5.2",
"dist" : {
"url" : "https://github.com/WordPress/WordPress/archive/4.5.2.zip",
"type" : "zip"
"source" : {
"url" : "https://github.com/WordPress/WordPress",
"type" : "git",
"reference" : "4.5.2"
"require" : {
"fancyguy/webroot-installer" : "1.0.0"
"type": "composer",
"url": "https://languages.koodimonni.fi"
"autoload": {
"psr-0": {
"Acme": "src/"
"config" : {
"vendor-dir": "content/vendor"
"extra" : {
"installer-paths": {
"content/plugins/{$name}/": ["type:wordpress-plugin"],
"content/themes/{$name}/": ["type:wordpress-theme"]
"webroot-dir" : "wp",
"webroot-package" : "wordpress",
"wordpress-install-dir": "wp",
"dropin-paths": {
"content/languages/": ["vendor:koodimonni-language"],
"content/languages/plugins/": ["vendor:koodimonni-plugin-language"],
"content/languages/themes/": ["vendor:koodimonni-theme-language"]

Go back to Sourcetree and create a commit with this file. This will ensure we can then modify the file and see what will be changing through out the toturial. We’ll go over some of the properties defined in this file.

You should change the “name” property to mimic what your user and repository name are. The description and author or authors you can leave them empty. It’s up to you.

Now the require and require-dev properties list what your composer controlled site requires on production and development environments. By default development environment will install production environment as well (more on this later). Now we have the first reference to modules and WP core, specifying the any subversion of the 4.5 version of WP, and specific versions for each plugin. For the sake of the exercise, these are not the latest versions for the plugins. You can go into WordPress Packagist and lookup latest versions for each plugin.

Now let’s open up a Terminal on the repository’s path and let composer do it’s job:

composer install

Fire up your file browser and explore your newly installed files. You’ll find the WordPress core under ./wp/ and plugins and themes under ./content/. You can configure these paths in composer.json under the extra property.

Another important thing that happened when executing composer install, it’s that composer created a lock file called composer.lock, because it didn’t exist. This file explicitly declares the particular versions and commits available at the moment of creation. Whenever someone executes ‘composer install’, composer will use the contents of this file and not composer.json. To update composer.lock from composer.json you have to execute ‘composer update’.

This is because not always you’ll have a specific version in the .json file, but the only way to make sure a software works is based on the tested version. Let’s commit this composer.lock file to see how that would work.

Let’s modify the composer.json file. We’ll change only versions of some plugins and the wordpress core. The core version needs to be specified, but the versions on plugins can be left as “latest” using ‘*’ as the version:

Change these lines in the require and require-dev properties (from line 10:- "wpackagist-plugin/w3-total-cache": "0.9.2",
+ "wpackagist-plugin/w3-total-cache": "*",
- "wpackagist-plugin/debug-bar": "0.8.2",
+ "wpackagist-plugin/debug-bar": "*",
- "wpackagist-plugin/debug-bar-console": "0.3"
+ wpackagist-plugin/debug-bar-console": "*"
And for wordpress, replace all three appearances of 4.5.2 by 4.5.3 under wordpress package definition (from line 25).- "version" : "4.5.2",
+ "version" : "4.5.3",
- "url" : "https://github.com/WordPress/WordPress/archive/4.5.2.zip",
+ "url" : "https://github.com/WordPress/WordPress/archive/4.5.3.zip",
- "reference" : "4.5.2"
+ "reference" : "4.5.3"

After that, execute composer update. It will also execute composer install as part of the process and update the needed files:

Be sure to commit composer.json and composer.lock into a new commit to keep the changes. Now that you’ve seen how fast and controlled an update can be. Let’s pretend the update didn’t work, and go back to the previous commit. Right click on the previous commit and use checkout. It will ask for a confirmation. Go with it and press Ok.

After executing composer install (not composer update this time, because we already have a composer.lock there) all files will be rolled back to the previous version.

Let’s again checkout the latest commit and use composer to install the latest versions of out dependencies to keep on advancing.

Working with a theme through Composer

You’ll need to clone your theme repository inside the themes directory. This directory is not created yet, as no themes have been installed, so you’ll have to create it. The configure path (as it is in composer.json) will be ‘your-site-repo/content/themes/’. Be sure to create and clone it in the correct path.

You will have to create a composer.json file as well for this repository. As before, the name doesn’t have to resemble your repository name in github but it is recommended. That is the only important part as this will be the reference to integrate it into your site repository.

"name": "halles/wp-composer-theme",
"authors": [
"name": "Matias Halles",
"email": "matias.halles@gmail.com"
"type" : "wordpress-theme",
"require" : {
"composer/installers": "~1.0"

Now your repository should look like this:

Now the tricky part. You need to “push” the commit from the theme repository into the server to allow the site’s repository to use it. After we need to add the package information to out site’s composer file, and also instruct it to install it from that package:

Into the require property add:"halles/wp-composer-theme": "dev-master",And under repositories:{
"type": "git",
"url": "git@github.com:halles/wp-composer-theme.git"

Be aware that the version for “halles/wp-composer-theme” is “dev-master”, which translates to “last commit on master branch”. You can use tags and branches this way. Now your modified composer.json should look like this:

Now, you should be able to execute composer update and connect both repositories successfully. The output should look like this:

Now your new composer.lock file has been created. And it is ready for letting you work on your theme. Let’s commit this and let’s add WordPress valid stuff into the theme. I just shamelessly copied files from an old theme located on bitbucket. Made it into a few commits in the theme, pushed the commits, and then updated our composer.lock file in the site, twice. Also added a new plugin and updated composer.lock file. All for the sake examples. Take a look at the history at github on the example repositories.

Now, the whole process might feel a bit complicated. But once you get used to it, adds no over head and let’s you version control your site code base and share it across the team with never fearing you’ll loose code when sharing.

Now, what about deployment.

This is the fun part. Once you have both repositories working in sync, deployment and updating among team mates or across any server or environment.

If you are cloning a project, all you have to do is clone it, and then install with composer:

Now, all your files are downloaded, ready for usage by your server 🎉

About customized paths for WordPress

As you remember, we didn’t install WP under the root of the project, and replaced ‘wp-content’ by ‘content’. So this won’t work properly straight away yet. We need to make some special configurations:

  • A custom index.php file which will bootstrap WP on the new path
  • Custom directives in wp-config.php file to make WP aware of these new paths
  • Since using git is incompatible with editing the theme files from the embedded editor, we need to deactivate the editor, also through the wp-config.php file.

I will add a default wp-config-sample.php file in the repository with the directives. Remember we added wp-config.php to our .gitignore file? When downloading your site onto the server you will have to make a copy of this sample file named wp-config.php and edit it. The wp-config.php is not supposed to be saved or tracked by git, because it is the file that will hold sensitive information: db connection information and hash salts.

Out index.php file will look like this. Notice the only difference in this file when compared to the index.php in WordPress’ core files is the /wp/ we are adding to the path of the require directive:

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

And our wp-config-sample.php file looks like this:

* Basic WP Config
define('DB_NAME', 'db_name');
define('DB_USER', 'db_user');
define('DB_PASSWORD', 'db_pass');
define('DB_HOST', 'db_host');
define('DB_CHARSET', 'utf8mb4');
define('DB_COLLATE', '');
$table_prefix = 'wp_';# Rememeber to get your salts at http://api.wordpress.org/secret-key/1.1/salt/define('AUTH_KEY', 'y%*XT).2%GS9D(DDaPmaH|dg8)BMh*>$w+(S2vWH!=avSS>Q9sdLMn<$`sv<a/!Z');
define('SECURE_AUTH_KEY', 'Bs >->v>0%`&2{^o^OP9Ta|wu9ESalMU?Y-^*Dd?Q$Fn:d{F:TmBp=r?nrW$dX<#');
define('LOGGED_IN_KEY', ';V-M%Z3M[IoJ{q_73gE25+-M@ge}B80DhkSRvx:Ax,/5gB@!2IOH4fv@kjbD()T`');
define('NONCE_KEY', 'qwsK_l*Tx)>X%bLm2+:1z%8a6|.+Yz7S=T2 +m<1jD!P2,pQ=R5(3aYl76W&{=tU');
define('AUTH_SALT', '):>bXn.2rVQ=m-mx[|^b!-e LKE`__Lp0V;zC>bB+--</Pnh3@cX7-2f=`RXM:eS');
define('SECURE_AUTH_SALT', 'Juey96K7;<KDIB>-8egHPge<+!rtt>OnK&:G$/bb[G-OWdfWx4{y9Kk?ka(GK7rb');
define('LOGGED_IN_SALT', 'Bb*on-)LpeuPEul0<GTY}*+|aQ--a;-QK/$ad)8b&oY+TkS)<]= z+(UI]jP]4pm');
define('NONCE_SALT', 'q!eu@*@_P3=!ovElTnP^?/1-`-tfvFa;Y;n%SizxtM*O2->V|%-hV*=O!Zx m!wy');
define('WPLANG', EN_us);/**
* Custom WordPress Install Path
# Sets the site's admin location and the site's location, respectively
define( 'WP_SITEURL', 'https://yourhost.io/wp' );
define( 'WP_HOME', 'https://yourhost.io' );
# Sets the content location, related to what's defined on composer.json file
define( 'WP_CONTENT_DIR', dirname( __FILE__ ) . '/content' );
define( 'WP_CONTENT_URL', WP_HOME . '/content');
# Sets the plugins location, related to what's defined on composer.json file
define( 'WP_PLUGIN_URL', WP_CONTENT_URL . '/plugins' );
# Disables the embebeded editor
define( 'DISALLOW_FILE_EDIT', true);
define( 'DISALLOW_FILE_MODS', true);
define( 'RELOCATE', true);
# Disables automatic update functions
define( 'WP_AUTO_UPDATE_CORE', false );
* You might want to force SSL on the admin page
# define( 'FORCE_SSL_LOGIN', true );
# define( 'FORCE_SSL_ADMIN', true );
* Debug Flags
* Use them under development environments
define('WP_DEBUG', false);
define('WP_DEBUG_LOG', false);
define('WP_DEBUG_DISPLAY', false);
define('SAVEQUERIES', false);
/* KEEP OUT BELOW *//** WordPress absolute path to the Wordpress directory. */
if ( !defined('ABSPATH') )
define('ABSPATH', dirname(__FILE__) . '/');
/** Sets up WordPress vars and included files. */
require_once(ABSPATH . 'wp-settings.php');

And that’s all folks.

This we do at Wikot to share work among our dev teams in several countries in an efficient and orderly way. Also, this let’s us have a clean overview of whatever else we may have in the directory, as well as backup uploaded media in a clean way when regular storage is used. More important, it stops us from wanting to kill each other 😅

If you have any questions, you can find me easily on twitter by @halles :)



Matías Halles

I like computers, most people, music, the outdoors and parties. And food. Tech Director @Wikot Chile. Founder at @faqwomen, @faqmen, @zoup, @electrofilia, et al