Deploying WordPress Plugins with Travis CI

Ever since I built my first WordPress plugin, the process of deploying the code to the Subversion repository has been a painful one. With Git as the widespread VCS flavor of choice, switching to SVN for plugin deployment has always been jarring and felt alien to my workflow. In this post I’m going to show you how I recently leveraged one of our existing tools to automatically deploy a plugin, and thus meaning I never have to touch SVN again!

There are lots of scripts out there to take care of the heavy lifting at deploy time, and we actually use a custom build script for our plugins, based on a specific repo structure, that also handles the deployment to However, I’ve recently been working on our Mergebot application which has automatic deployments to the server when code is pushed to the master branch, I’ve gotten used to not having to physically do anything to deploy the code!

The Mergebot WordPress plugin has been having releases almost daily in the run up to opening up the beta, and this has meant performing the manual deployment each time. Sure, running a single command-line script is not much effort, but hey, I’m a lazy streamlined developer!

I had a eureka moment the other week whilst listening to a talk on deployments by Michael Heap. He was going through different solutions for deploying applications with varying levels of complexity, and started to talk about Continuous Integration with a tool like Jenkins or Travis CI. We use Travis CI only to run unit tests on our plugins during development and before releases. I realised we were missing a trick and only utilizing part of Travis’ potential!

What is Continuous Integration?

This is the process of developers maintaining a shared repository of code, which is committed to frequently (typically daily), and is then automatically built and tested to ensure everything is working and to catch issues early. A CI server is used to do the following:

  • Check out the code
  • Build the code
  • Run tests
  • Automate deployment

For the most part this is how we use Travis for our plugins. If you haven’t seen Travis before then I recommend taking a look. It’s free for open source projects, so public repositories on GitHub. The simplest use case, and one we do for all our plugins, is getting Travis to run our unit tests for every push and every pull request.

Setting up PHPUnit for unit testing a plugin is relatively simple but probably deserves its own post. As responsible developers we should be running these tests during development to ensure new code doesn’t affect existing code. Having Travis run these tests automatically so we don’t have to remember is awesome! Adding deployment to the list of tasks Travis performs turns it into a continuous delivery service as well, automation FTW!

Deployments with Travis

Travis has many service integrations to deploy to, such as S3, GitHub (in the form of release tags), Heroku, and lots more. However, there is nothing specific for the WordPress plugin repository, which is understandable. But luckily Travis provides the ability to run a deployment script as a post-build task so we can accomplish our own bespoke deployments.

The following part of this post is a tutorial to get you up and running using Travis to automatically deploy your plugin to the WordPress repo. I’m going to make a few assumptions for this tutorial to work:

  • You have a WordPress plugin on the repo, but also hosted on GitHub
  • You are already using Travis
  • Travis runs some form of build script or your GitHub repo is in a deployable state

Deployment Script

First things first, let’s add a new script to your project which will eventually perform the deployment. Our GitHub repos are set up with the actual plugin code inside a src directory, here’s an abbreviated example:

|-- src
|   |-- mergebot
|   |   |-- mergebot.php
|   |   |-- readme.txt
|-- tests
|-- composer.json
|-- .travis.yml

So I’ll just create a new scripts directory and a new file inside it, with the correct permissions so the Travis user can execute it:

mkdir scripts
touch scripts/
chmod ugo+x ./scripts/

Travis Configuration

Then we need to tell Travis to actually run the script as part of the deployment process, so we need to make some edits to our .travis.yml file, adding this section to the bottom:

  # Auto-deploys the built plugin to on push to master branch
  provider: script
  skip_cleanup: true
  script: ./scripts/
    branch: master
    php: 7.0

The provider option is set to script as we are doing a custom deployment using our script we provide with the script option path.

I have enabled the skip_cleanup option, which just tells Travis to not bother cleaning up files and assets that might have been created during testing. This is because our plugin build script produces a zip file that we will use as our deployment source, so there is no need to get Travis to clean up files required for deployment.

We then tell Travis to only run this deployment after a build on the master branch, and as we have multiple configurations in our build matrix, we tell Travis to only deploy on PHP 7.0, so the deployment only happens once.


The is quite specific to our plugin in places, but I’ve stripped it back and will run through what it does and the places you will want to tweak:

There are three conditionals at the start of the script that ensure the script is being run by Travis, the WP_ORG_PASSWORD variable is set, and we are on the correct master branch. The password is set as a secure environment variable in Travis so we don’t have to store it in Git. It also doesn’t get displayed in the Travis build log. You set the variable from the settings screen in Travis:

Travis environment setting

Lines 18-24 set a bunch of variables that are used throughout the script, some of which will need to be set specific to your build process:

  • WP_ORG_USERNAME – our username (could be set as an environment variable)
  • PLUGIN – the plugin slug on and the same in the repo
  • PROJECT_ROOT – the root path of the checked out repo in the Travis container
  • PLUGIN_BUILDS_PATH – the directory where our built plugin zip is stored
  • PLUGIN_BUILD_CONFIG_PATH – the build config directory
  • VERSION – the built plugin version, derived from the version in the src/ dir
  • ZIP_FILE – the path to the built zip file, generated earlier on by Travis

The script then checks that the zip file exists, and also ensures that the version we want to deploy doesn’t already exist as a tagged version in the WordPress plugin SVN repository, and therefore has already been deployed.

The zip file is then unzipped, the SVN repository checked out to a local directory, and the SVN fun begins! First of all we move the checked out trunk directory to another location so we can use it later. We then recreate the directory and copy all of our built plugin files into it.

At this point, we really just want to move the checked out svn\trunk.svn SVN special directory into our new trunk so SVN can work out what has changed. However, Travis containers by default have installed the official Ubuntu packages (precise) of Subversion, which is currently 1.6.17. Before Subversion 1.7, all directories in a working copy have a .svn config directory, which means that we can’t just move back the directory from the root of trunk. Lines 67 – 77 goes through all of the directories inside the checked out trunk, and copies the svn directory to the new trunk, if the path still exists. Nasty, huh?

Once that’s done, we create a new tag directory for the version we are deploying, and run some grep magic with svn stat a couple of times, basically to add new files to working copy, and remove others that have been deleted. Then we commit back up to the repository, passing in our credentials for authentication. We also pass the --no-auth-cache flag to stop SVN asking for us to store our credentials, as there will be no user to respond in the Travis container, and the build will stall and effectively never complete.

A little bit of cleanup to finish, and we are done: Travis deploying our plugin to, awesome!

Debugging Travis

I found working on this quite challenging, as even though you could test running locally (most of the time with the svn ci line commented out!), the Travis environment sometimes did things I wasn’t expecting, and you have to trigger builds and wait for them to run to test things out. However, a great way to debug this more easily is running Travis locally with Docker. This was invaluable in helping identify issues, such as the SVN 1.6 version issue.

After I had installed Docker and downloaded the travis-php image (this takes a while!), I used this set of commands to recreate my Travis build:

If your GitHub repo is private, just add in your username and password as basic auth credentials to the clone URL. Again, some of the commands are specific to Mergebot and our build process, but they give you an idea of what you need to do to recreate the process.

Dependency Management

Two of the main problems I faced during the development were around dependencies. Mergebot uses a couple of third party packages that we inject into the built plugin’s vendor dir with Composer and a custom directory installer package. But it turned out our build process was running composer install too late, therefore the vendor didn’t exist in the built plugin. This wasn’t an issue when we were just running our unit tests with Travis or building the plugin locally for deployment. But running through the process with Travis highlighted the issue and our build process needed to be tweaked.

As the script was almost complete and ready to go, I was investigating the output of svn stat to see if the changed files were exactly the same as if I ran it locally. It turned out that some asset files which were generated by Grunt at build time were flagged as modified. I didn’t want to start deploying code built on Travis that resulted in unexpected file changes.

So after a bit of digging I got to the bottom of it. I fully expected the results of npm install on any server to be the same, as per the versions set in package.json. However, unlike Composer, npm has no lock file (unless you use shrinkwrap), and therefore dependencies of your required packages can vary from install to install. Yarn to the rescue! Yarn is easy to swap in, and creates a yarn.lock which ensures complete version parity for the dependencies anywhere they are installed.

Post Deployment

An automated task isn’t complete without a notification, and as we are Slack users here at Delicious Brains HQ, I added a message after a successful deployment. Slack makes it super easy to add incoming data to channels.

You will need to add a new Slack WebHook to get your team’s Slack URL via Apps & Integrations > Manage > Custom Integrations > Incoming WebHooks, and adding a new configuration. Here is the code I added to the end of the deployment script:

curl -X POST \
-H 'Content-type: application/json' \
--data '{"username": "Travis CI","icon_url": "","text": "Mergebot plugin version '"$VERSION"' deployed to <|> :tada:"}' \

Travis Slack deployment notification

I hope that has proved useful. How do you handle deploying WordPress plugins, have you gone fully automated yet? I’d be interested to know if people are using other CI or deployment solutions for this problem. Let me know in the comments below.

About the Author

Iain Poulson Product Manager

Iain is a product manager based in the south of England. He also runs multiple WordPress products. He helps people buy and sell WordPress businesses and writes a monthly newsletter about WordPress trends.