WordPress theme development with Gulp, Bower, and Sass

Update February 2015: this post is now out of date as the starter kit has evolved. I’m working on a new post to explain all the developments; in the meantime please check out the original repo on GitHub. Thanks!

WordPress theme development is remarkably more efficient with Gulp, a Node-based build system and task runner similar to Grunt. Gulp has been widely hyped in recent times—and no wonder, it’s a major time-saver. I found out about Gulp by way of this excellent introduction by Mark Goodyear, later adapting the build script in this post by Matt Banks for my own use.

Since then I have refined my workflow to the point where I figure it might be helpful for me to share my approach with the WordPress community. With this in mind I have prepared this starter kit (with source code on GitHub) to demonstrate how you can use Gulp with Bower and Sass to optimize your WordPress theme development workflow. This starter kit does not contain any actual WordPress theme files—it is meant to act as the scaffolding that surrounds your theme and supports your workflow.

Working with Gulp requires that you know your way around the command line. You’ll need to have installed Gulp, Bower, and Sass already, of course. (And hopefully I don’t have to tell you that both Bower and Sass are awesome!) Follow the instructions on GitHub or Google around a bit and you should be up and running in no time.

Configuration is a snap; just rename the project variable at the top of gulpfile.js to whatever you want your theme directory to be named. The src directory presently contains nothing more than a sample of code showing how you might want to include the generated scripts and stylesheet in your theme.

Another component you will want to setup is LiveReload, a great tool that will allow Gulp to refresh your browser anytime you make changes to theme files. I use a Chrome browser extension but there are many other options out there if you go looking.

Next you may as well drop some code into your new theme directory so that you have something to play with. _s is not a bad place to start. You can also add a pre-existing theme, though you may want to move some files around, especially if that theme has Sass source files.

Finally, you will want to symlink your new theme to a local installation of WordPress so you can work on it. There are two directories you’ll want to consider symlinking after setup: build (for development and testing) and the project folder under dist (for testing and deployment to production). Once you start mucking around this stuff will become clear.

At any rate, when setup is complete (which shouldn’t take more than a few minutes once you’re familiar with the process) all you need to do is run gulp. Now, anytime you modify a file, Gulp will draw upon an arsenal of plugins to concatenate, autoprefix, lint, minify, rename, and copy your stylesheets, scripts, and images as needed. When you’re read to deploy something to production you can build a clean distribution with gulp dist. This task is more memory-intensive as images are compressed each time. You’ll want to do this before deploying a theme—but while you are still developing it locally simply running gulp is good enough.

Let me walk you through parts of the build script:

// Project configuration
var project     = 'my-theme'
  , build       = './build/'
  , dist        = './dist/'
  , source      = './src/' // 'source' instead of 'src' to avoid confusion with gulp.src
  , bower       = './bower_components/'

// Initialization sequence
var gulp        = require('gulp')
  , gutil       = require('gulp-util')
  , plugins     = require('gulp-load-plugins')({ camelize: true })

Specify the name of your theme directory by modifying the project variable. The next batch of variables specify various paths that will be used in the remainder of the script. Gulp is then loaded alongside various dependencies. gulp-load-plugins is a handy tool that will read package.json and require whatever plugins are specified there, saving us the need to explicitly require plugins.

gulp.task('styles', function() {
  return gulp.src([source+'scss/*.scss', '!'+source+'scss/_*.scss']) // Ignore partials
    loadPath: bower // Adds the `bower_components` directory to the load path so you can @import directly
  , precision: 8
  .pipe(plugins.autoprefixer('last 2 versions', 'ie 9', 'ios 6', 'android 4'))
  .pipe(plugins.rename({suffix: '.min'}))
  .pipe(plugins.minifyCss({ keepSpecialComments: 1 }))

This is a typical Gulp task for handling Sass files. Here we are telling Gulp to read all .scss files from the src/scss directory that don’t start with an underscore and pipe them through the Sass compiler. Note that I haven’t included Compass, though you may want to. With Autoprefixer in the toolchain I find most Sass libraries somewhat useless. (And if you’re looking for a Sass mixin library for the post-vendor prefixing era try Scut.) At any rate the resulting CSS file ends up in build for debugging purposes in both uncompressed and minified forms. The keepSpecialComments flag ensures that the minified CSS still includes the header required by WordPress.

// Scripts; broken out into different tasks to create specific bundles which are then compressed in place
gulp.task('scripts', ['scripts-lint', 'scripts-core', 'scripts-extras'], function(){
  return gulp.src([build+'js/**/*.js', '!'+build+'js/**/*.min.js']) // Avoid recursive min.min.min.js
  .pipe(plugins.rename({suffix: '.min'}))

// Lint scripts for errors and sub-optimal formatting
gulp.task('scripts-lint', function() {
  return gulp.src(source+'js/**/*.js')

// These are the core custom scripts loaded on every page; pass an array to bundle several scripts in order
gulp.task('scripts-core', function() {
  return gulp.src([
  , source+'js/navigation.js'

The current implementation of the scripts task differs remarkably from the original outlined by Matt Banks. Here there is a master scripts task that waits on a bunch of other tasks to generate source files before minifying whatever scripts can be found in the build directory. First, all scripts under src are linted (checked for errors against the settings in the .jshintrc file), and then two bundles are concatenated and deposited in the build directory.

As you can see you’ll need to manually specify files for each bundle1. Bundling gives you, the theme developer, more control over what gets loaded and where. Say, for instance, you have a script that you only run on the front page; in that case you can generate a secondary bundle and call that conditionally from within WordPress rather than load it up on every page. In the example listed core.js is concatenated with navigation.js before being shipped off to the build directory for minification. You can, of course, also include dependencies from the Bower components folder; just add a path such as bower+'dependency/dependency.js' to the source array.

Image and PHP file handling is simple and straight-forward: files that match the relevant patterns are copied to the build directory with paths intact. If you store PHP files in a subdirectory they’ll land in the same spot under build.

Next is a series of tasks to handle packaging. I won’t include the source code in this write-up but I will explain what’s going on. First, junk files are cleared out and the dist folder is wiped clean. CSS files are minified, images are optimized, and everything is deposited in a subdirectory of dist matching the name of your project variable. This way you can upload only this folder to your production environment without having to rename anything.

Next is a hack to copy and rename normalize.css so that it can be properly imported in Sass. This task is called by Bower when front-end components are added or updated (see .bowerrc for more). You may find other uses for the Bower post-install trigger depending on what front-end components you integrate into your project.

// Start the livereload server; not asynchronous
gulp.task('server', ['build'], function() {
  plugins.livereload.listen(35729, function (err) {
    if (err) {
      return console.log(err);

// Watch task: build stuff when files are modified, livereload when anything in the `build` or `dist` folders change
gulp.task('watch', ['server'], function() {
  gulp.watch(source+'scss/**/*.scss', ['styles']);
  gulp.watch([source+'js/**/*.js', bower+'**/*.js'], ['scripts']);
  gulp.watch(source+'**/*(*.png|*.jpg|*.jpeg|*.gif)', ['images']);
  gulp.watch(source+'**/*.php', ['php']);
  gulp.watch([build+'**/*', dist+'**/*']).on('change', function(file) {

The watch task watches for file changes and calls the appropriate tasks as needed, only triggering a browser refresh via Live Reload when files in the build or dist directories have changed. This task is chained to server which is then chained to build. This means that when you run watch a series of tasks will fire ensuring that you’re always working with a freshly minted copy of your theme. Consequently, the default task simply fires watch when you run gulp from the command line.

Apart from the main build script this project also includes a few handy files:

  • .bowerrc to run the aforementioned Bower post-install script (in this case gulp bower_components).
  • .gitignore and .gitattributes files adapted for use with WordPress theme development.
  • .jshintrc settings for JSHint.
  • bower.json pre-loaded with Normalize.css and Eric Meyer’s reset. Edit this file to give your project a name and take credit for your work!
  • packages.json pre-loaded with all the Gulp development dependencies you need. Edit this file to give your project a name and take credit for your work!

You will notice that this project is setup to generate a clean theme directory for deployment and distribution. For some reason I continually run across WordPress themes hosted on GitHub littered with all manner of development files (.gitignore and so on). There’s no need for this—just build into a clean directory. The only downside is that you probably won’t be able to update your WordPress theme from GitHub, but that’s not a show-stopper.

If you dive into src/scss you’ll find a handful of files demonstrating how to import Bower components. In the case of Normalize.css I have a custom task (above) to copy the file into the assets directory but the Eric Meyer’s reset is imported directly. Bower components are in the Sass load path so you don’t have to mess around with ../../.. nonsense.

With all that said, Gulp is not a panacea and Grunt is certainly not obsolete. I happen to find Gulp fast and easy to work with but it isn’t nearly as feature-rich as Grunt—not yet anyway. I agree with what Matt Banks wrote in his original article: Gulp lacks a robust suite of deployment plugins. But apart from that Gulp is an incredibly useful tool for WordPress theme development.

I hope you have found my WordPress-Gulp-Bower-Sass starter kit helpful in some way!

  1. Automated script bundling is a feature I’d like to add in the future. I tried a few approaches recently but wasn’t satisfied with the results. For now, unless you’re managing dozens of dependencies, a human touch will probably offer more control and accuracy than some kind of automated kludge. 

Related posts

16 responses

  1. Hello,

    Thanks for the post.

    I think I understand pretty much everything when it comes to setting up gulp and understanding the structure you described here. What I dont understand is how to link it with wordpress. Should this whole folder be a template inside wp-content? Where exactly should be placed? You are not clarifying that yet unfortunately.


    1. I have taken some time to revise this article and update it with my new workflow… but to answer your question (and assuming you are developing locally on OS X) you can develop your theme anywhere you want as long as you symlink the theme folder to your wp-content directory. For example: ln -s ~/dev/work/theme/ ~/dev/localhost/wp-content/theme/ (but of course your directory structure will differ from this).

  2. Hey Alexander thanks a lot for this article.
    I have a question – Is there a specific reason to symlink the theme folder to wordpress instead of just working in your theme inside wordpress already?
    I’m confused into the how and why of this symlink.

  3. Symlinking is simply a matter of convenience for working with WordPress themes that involve a build process. If you’re working with Gulp, Bower, and Sass you’re going to have a lot of extra directories and files laying around that aren’t actually a part of your theme. You could, of course, build your theme directly into wp-content/themes, but symlinking accomplishes the same thing and lets you keep everything you need for theme development in one place.

    Either way, if you’re not familiar with symlinking it’s a useful technique to pick up for local development :)

  4. Very nice project. I really like the separation of source, build and production directories. Together with the idea of symlinking this will be my future workflow for sure.

    I have some problems integrating some existing starter themes though. Maybe you could point me into the right direction. For example, how would you begin to put https://github.com/olefredrik/FoundationPress into your scaffolding?

    1. That’s a tough one. You’d have to put some work into rearranging a complex, opinionated starter theme like FoundationPress, especially since it ships with its own build process and obeys different conventions. But to answer your question, how to begin? I’d tear out all the original WordPress stuff (mainly the PHP files) and install Foundation and its various dependencies via Bower then wire everything up as needed by making reference to the Foundation docs. You’d be reinventing the wheel somewhat… but you’d end up with a cleaner and more easily maintained environment with which to work with this particular starter theme.

  5. Thanks for sharing this workflow! Everything works great for me except for the livereload part. I made a symlink from the /build folder to my themes folder and am running MAMP to serve up localhost. I have tried both the FF and Chrome addons for LiveReload but neither works.

    I’ve gotten gulp-connect to work with livereload in a different workflow before but in that case gulp was actually serving at its own port on localhost (which is great except it wont read in php files to my knowledge). Any insights on why livereload isnt working when i make file changes? I havent changed anything in the gulpfile.

    Thanks again!

    1. Michael: have you been able to verify that Livereload is running on port 35729? If you don’t see any error messages when you run gulp the problem might be with your browser extension setup. Double-check you’ve actually enabled LR in Chrome (instead of just adding it).

  6. Hi Alexander,

    thanks for this great dev workflow! Can’t wait to use it for my next project. I’ll also check the combination with FoundationPress or Google Web Starter Kit.

    Keep up the good work plz! :)

    Greetz from Germany

  7. That looks amazing.

    But though all the gulp/bower/sass related information is ok for me, I really have no clue on how to integrate it with WordPress. I tried used “ln -s ” but that doesn’t seem to work. Could you explain that in some detail?


  8. Sure, I’ll use some examples from my local development environment. I have WordPress installed at ~/dev/localhost/wp. I develop my theme in ~/dev/work/pendrell. To link these two up I might enter ln -s ~/dev/work/pendrell/build ~/dev/localhost/wp/wp-content/themes/pendrell-build. WordPress now detects a theme installed in the pendrell-build folder which happens to point to the build directory where I am actively developing the theme. When I run gulp any changes I’ve made to the src of the theme appear immediately. No need to copy files or change anything else from that point onward.

    If you’re serious about testing things before production you might do the same for the dist folder and then switch themes prior to pushing changes to QA or production.

    Of course you’ll need to have all the necessary templates in src; this is not a starter theme, as noted in the text ;)

  9. This is a great project! I successfully set up everything, and now have a nice working copy on my computer hooked up to my local wordpress. Thing is that the LiveReload didn’t work right away, so I’ll have to tinker with that a bit. Thanks for all of your hard work, this is really a good starting point for a clean wordpress theme that works with everything I’d want to have (plus libsass! lots use ruby-sass etc).

  10. HI Alexander,

    first of all, thanks a lot for your post, it inspire me to build my own build app as a way to learn Gulp.

    I’m having some issues with the browserSync plugin not refreshing the pages. I use MAMP at 8888 port and my browserSync is proxy: ‘localhost:8888’ and port: 8888,

    here is my project, could you point me in the right direcction?

    Thanks a lot


Markdown and HTML enabled in comments.
Your email address will not be published. Required fields are marked *