Sage 9 Beta 3 WooCommerce Setup

Since I wrote my last post on setting up WooCommerce in Sage 9 things have changed once again. Sage 9 beta 3 has adjusted its structure and looks even more like a Laravel app. And WooCommerce has also been adjusted and since WooCommerce 3.0 several patches have been added. So let’s discuss the Sage 9 Beta 3 WooCommerce Setup shall we?

Sage Views

There are no longer template files anymore. That is, there are, but they are now all added under /resources/views . Not under templates anymore. Here is the structure now:

site/web/app/themes/sage/resources/views $ tree -L 2
├── 404.blade.php
├── archive-product.php
├── index.blade.php
├── layouts
│   └── app.blade.php
├── page.blade.php
├── partials
│   ├── comments.blade.php
│   ├── content-page.blade.php
│   ├── content-search.blade.php
│   ├── content-single.blade.php
│   ├── content.blade.php
│   ├── entry-meta.blade.php
│   ├── footer.blade.php
│   ├── head.blade.php
│   ├── header.blade.php
│   ├── page-header.blade.php
│   └── sidebar.blade.php
├── search.blade.php
├── single-product.php
├── single.blade.php
├── template-custom.blade.php
├── woocommerce
│   ├── archive-product.php
│   └── single-product.php
└── woocommerce.blade.php

As you can see all .blade.php files are now under resources/views like they are in a Laravel app.

WooCommerce Templates

We still seem to need to add archive-product.php and single-product.php to make WooCommerce work properly and have the content we add for WooCommerce loaded there. These seem to work from the views directory or views/woocommerce. But we do now have a way to use Blade templating by making them load woocommerce.blade.php.

This we can do with a simple:

<?php echo App\Template('woocommerce'); ?>

WooCommerce Blade file

The woocommerce.blade.php file is where we do all our magic and CAN use Blade. For a basic display we add:


This so we can load the basic app layout and the general WooCommerce content using:


As you can see this is a great step forward integrating Blade into WooCommerce!

Bootstrap Navigation in Sage 9

If you are working with Bootstrap and like most Sage developers are brave enough to work with the Bootstrap 4 beta with all it’s goodies you will often want to add a Bootstrap menu. So how is a Bootstrap Navigation in Sage 9 done? Here is how.

Sage 9 and Bootstrap

When you install Sage 9 do remember to pick Bootstrap as your CSS framework. This way the latest Bootstrap 4 will be added as the framework of choice. And that way the scss files needed to make Bootstrap 4 run will be added to your theme.

Partials Header

To load the navigation with the proper html tags edit the default templates/partials/header.blade.php and make it have this code for content:

<header class="banner">
 <div class="container">
 <nav class="navbar navbar-toggleable-md navbar-light bg-faded">
<button class="navbar-toggler navbar-toggler-right" type="button" data-toggle="collapse"
 data-target="#navbarSupportedContent" aria-controls="navbarSupportedContent" aria-expanded="false"
 aria-label="Toggle navigation">
 <span class="navbar-toggler-icon"></span>
<a class="navbar-brand" href="{{ home_url('/') }}">{{ get_bloginfo('name', 'display') }}</a>
<div class="collapse navbar-collapse" id="navbarSupportedContent">
 @if (has_nav_menu('primary_navigation'))
 {!! wp_nav_menu(['theme_location' => 'primary_navigation', 'walker' => new wp_bootstrap_navwalker(), 'menu_class' => 'navbar-nav nav pull-right']) !!}
 <!-- <form class="form-inline collapse navbar-collapse pull-right">
 <button class="btn btn-outline-success" type="button">Main button</button>
 </form> -->

This will make it work with the right html tags as well as CSS classes and IDs.

Custom Walker

You will of course also be needing a custom walker. And that walker should be loaded in the theme. In scr/  (app/ in beta 3) we add wp_bootstrap_navwalker.php:


 * Class Name: wp_bootstrap_navwalker
 * GitHub URI:
 * Description: A custom WordPress nav walker class to implement the Bootstrap 4 navigation style in a custom theme using the WordPress built in menu manager.
 * Version: 2.0.4
 * Author: Edward McIntyre - @twittem
 * License: GPL-2.0+
 * License URI:

class wp_bootstrap_navwalker extends Walker_Nav_Menu {

 * @see Walker::start_lvl()
 * @since 3.0.0
 * @param string $output Passed by reference. Used to append additional content.
 * @param int $depth Depth of page. Used for padding.
 public function start_lvl( &$output, $depth = 0, $args = array() ) {
 $indent = str_repeat( "\t", $depth );
 $output .= "\n$indent<div role=\"menu\" class=\" dropdown-menu\">\n";

 * Ends the list of after the elements are added.
 * @see Walker::end_lvl()
 * @since 3.0.0
 * @param string $output Passed by reference. Used to append additional content.
 * @param int $depth Depth of menu item. Used for padding.
 * @param array $args An array of arguments. @see wp_nav_menu()
 public function end_lvl( &$output, $depth = 0, $args = array() ) {
 $indent = str_repeat("\t", $depth);
 $output .= "$indent</div>\n";

 * Start the element output.
 * @see Walker::start_el()
 * @since 3.0.0
 * @param string $output Passed by reference. Used to append additional content.
 * @param object $item Menu item data object.
 * @param int $depth Depth of menu item. Used for padding.
 * @param array $args An array of arguments. @see wp_nav_menu()
 * @param int $id Current item ID.
 public function end_el( &$output, $item, $depth = 0, $args = array() ) {
 if($depth === 1){
 if(strcasecmp( $item->attr_title, 'divider' ) == 0 || strcasecmp( $item->title, 'divider') == 0) {
 $output .= '</div>';
 }else if ($depth === 1 && (strcasecmp( $item->attr_title, 'header') == 0 && $depth === 1)) {
 $output .= '</h6>';
 $output .= '</li>';

 * @see Walker::start_el()
 * @since 3.0.0
 * @param string $output Passed by reference. Used to append additional content.
 * @param object $item Menu item data object.
 * @param int $depth Depth of menu item. Used for padding.
 * @param int $current_page Menu item ID.
 * @param object $args
 public function start_el( &$output, $item, $depth = 0, $args = array(), $id = 0 ) {
 $indent = ( $depth ) ? str_repeat( "\t", $depth ) : '';

 * Dividers, Headers or Disabled
 * =============================
 * Determine whether the item is a Divider, Header, Disabled or regular
 * menu item. To prevent errors we use the strcasecmp() function to so a
 * comparison that is not case sensitive. The strcasecmp() function returns
 * a 0 if the strings are equal.

//( strcasecmp($item->attr_title, 'disabled' ) == 0 ) 
 if($depth === 1 && (strcasecmp( $item->attr_title, 'divider' ) == 0 || strcasecmp( $item->title, 'divider') == 0)) {
 $output .= $indent . '<div class="dropdown-divider">';
 }else if ((strcasecmp( $item->attr_title, 'header') == 0 && $depth === 1) && $depth === 1){
 $output .= $indent . '<h6 class="dropdown-header">' . esc_attr( $item->title );
 $class_names = $value = '';

$classes = empty( $item->classes ) ? array() : (array) $item->classes;
 $atts = array();
 $atts['title'] = ! empty( $item->title ) ? $item->title : '';
 $atts['target'] = ! empty( $item->target ) ? $item->target : '';
 $atts['rel'] = ! empty( $item->xfn ) ? $item->xfn : '';
 $atts['href'] = ! empty( $item->url ) ? $item->url : '';

$id = apply_filters( 'nav_menu_item_id', 'menu-item-'. $item->ID, $item, $args );
 if ( in_array( 'current-menu-item', $classes ) )
 $classes[] = ' active';

if($depth === 0){
 $classes[] = 'nav-item';
 $classes[] = 'nav-item-' . $item->ID;
 $atts['class'] = 'nav-link';
 if ( $args->has_children ){
 $classes[] = ' dropdown';
 $atts['href'] = '#';
 $atts['data-toggle'] = 'dropdown';
 $atts['class'] = 'dropdown-toggle nav-link';
 $atts['role'] = 'button';
 $atts['aria-haspopup'] = 'true';
 $class_names = join( ' ', apply_filters( 'nav_menu_css_class', array_filter( $classes ), $item, $args ) );
 $class_names = $class_names ? ' class="' . esc_attr( $class_names ) . '"' : '';

$id = $id ? ' id="' . esc_attr( $id ) . '"' : '';
 $output .= $indent . '<li' . $id . $value . $class_names .'>';
 $classes[] = 'dropdown-item';
 $class_names = join( ' ', apply_filters( 'nav_menu_css_class', array_filter( $classes ), $item, $args ) );
 $atts['class'] = $class_names;
 $atts['id'] = $id;
 $atts = apply_filters( 'nav_menu_link_attributes', $atts, $item, $args );

$attributes = '';
 foreach ( $atts as $attr => $value ) {
 if ( ! empty( $value ) ) {
 $value = ( 'href' === $attr ) ? esc_url( $value ) : esc_attr( $value );
 $attributes .= ' ' . $attr . '="' . $value . '"';

$item_output = $args->before;
 $item_output .= '<a'. $attributes .'>';
 * Icons
 * ===========
 * Since the the menu item is NOT a Divider or Header we check the see
 * if there is a value in the attr_title property. If the attr_title
 * property is NOT null we apply it as the class name for the icon
 if ( ! empty( $item->attr_title ) ){
 $item_output .= '<span class="' . esc_attr( $item->attr_title ) . '"></span>&nbsp;';

$item_output .= $args->link_before . apply_filters( 'the_title', $item->title, $item->ID ) . $args->link_after;
 $item_output .= '</a>';
 $item_output .= $args->after;

$output .= apply_filters( 'walker_nav_menu_start_el', $item_output, $item, $depth, $args );

 * Traverse elements to create list from elements.
 * Display one element if the element doesn't have any children otherwise,
 * display the element and its children. Will only traverse up to the max
 * depth and no ignore elements under that depth.
 * This method shouldn't be called directly, use the walk() method instead.
 * @see Walker::start_el()
 * @since 2.5.0
 * @param object $element Data object
 * @param array $children_elements List of elements to continue traversing.
 * @param int $max_depth Max depth to traverse.
 * @param int $depth Depth of current element.
 * @param array $args
 * @param string $output Passed by reference. Used to append additional content.
 * @return null Null on failure with no changes to parameters.
 public function display_element( $element, &$children_elements, $max_depth, $depth, $args, &$output ) {
 if ( ! $element )

$id_field = $this->db_fields['id'];

// Display this element.
 if ( is_object( $args[0] ) )
 $args[0]->has_children = ! empty( $children_elements[ $element->$id_field ] );

parent::display_element( $element, $children_elements, $max_depth, $depth, $args, $output );

 * Menu Fallback
 * =============
 * If this function is assigned to the wp_nav_menu's fallback_cb variable
 * and a manu has not been assigned to the theme location in the WordPress
 * menu manager the function with display nothing to a non-logged in user,
 * and will add a link to the WordPress menu manager if logged in as an admin.
 * @param array $args passed from the wp_nav_menu function.
 public static function fallback( $args ) {
 if ( current_user_can( 'manage_options' ) ) {

extract( $args );

$fb_output = null;

if ( $container ) {
 $fb_output = '<' . $container;

if ( $container_id )
 $fb_output .= ' id="' . $container_id . '"';

if ( $container_class )
 $fb_output .= ' class="' . $container_class . '"';

$fb_output .= '>';

$fb_output .= '<ul';

if ( $menu_id )
 $fb_output .= ' id="' . $menu_id . '"';

if ( $menu_class )
 $fb_output .= ' class="' . $menu_class . '"';

$fb_output .= '>';
 $fb_output .= '<li><a href="' . admin_url( 'nav-menus.php' ) . '">Add a menu</a></li>';
 $fb_output .= '</ul>';

if ( $container )
 $fb_output .= '</' . $container . '>';

echo $fb_output;

This one is from a fork of the original Twittem repo. It has been created by @sebakerckhof . A bit older now, but despite some BS changes it works well.

Including Custom Walker

To load it inside the theme you should have this in resources/functions.php (in latest Sage 9 beta3)

array_map(function ($file) use ($sage_error) {
 $file = "src/{$file}.php";
 if (!locate_template($file, true, true)) {
 $sage_error(sprintf(__('Error locating <code>%s</code> for inclusion.', 'sage'), $file), 'File not found');
}, ['helpers', 'setup', 'filters', 'admin', 'wp_bootstrap_navwalker']);

The array_map(function..) is already there. You just need to add the file to the array there to have it included. This is different from working in Sage 9 so it may be a little getting used to.

Once you have done that the Bootstrap Navigation in Sage 9 will be a fact. Of course plenty of styling to be done and perhaps you want other classes for different displays within the page. But I am sure you will be able to work that out )) .

Sage 9 WooCommerce Setup

To set up Sage 9 with WooCommerce things have to be done slightly different from the setup in Sage 8. This mainly as Sage 9 works with the Laravel Blade templating system. It is mostly the same though. Here is how the Sage 9 WooCommerce setup is done.

Note Also see Sage 9 Beta 3 follow-up post

Steps to take

  1. Create archive-product.php and single-product.php in the theme root with <?php woocommerce_content(); ?>
  2. Add theme support in sage/src/setup.php (moved to app/ since beta 3) with add_theme_support('woocommerce');
  3. Add woocommerce.blade.php inside templates/woocommerce
  4. Add dummy content
  5. Start styling

WooCommerce Files

The WooCommerce files that are necessary as a bare minimum you can just copy from the plugin or as stated you can just use:

<?php woocommerce_content(); ?>

inside empty newly created files. The latter being much cleaner to start with. So go for that.

WooCommerce Theme Support

For the WooCommerce theme support you can use this snippet:

 * Enable WooCommerce Support in Theme
 * @link

Add it to your setup.php file inside theme/src  (moved to app/ since beta 3). This will activate WooCommerce support for your theme.

Missing Files Notices

Then you will still have notices like header.php missing:

Notice: Theme without header.php is <strong>deprecated</strong> since version 3.0.0 with no alternative available. Please include a header.php template in your theme. in /srv/www/ on line 3959

as well as footer.php and sidebar.php . You can add blank ones for starters to deal with these notices and add files later. They can be added to the root of the theme, but in the case of Sage you should really put them in the templates folder.

Mind you this only happens on a clean setup. Why these notices are shown and why these files are not added to the theme can be read here.

Dummy Content

Dummy content is inside the WooCommerce theme. So you can import it using the WooCommerce importer. Do not download it from another site as that fails most of the time. I did and it failed to import the images resulting in more notices.

Next Steps

The next steps for the Sage 9 WooCommerce Setup would be adding serious styling. But as usual it will be better to first work on the general header and theme navigation, footer and general pages before moving on to that. In Sage 9 you can choose your own CSS framework, Foundation or Bootstrap:

Select a CSS Framework



For my next theme I will be choosing Bootstrap. Once I have done some more work on it I will add another post with details on the single-product.php, product-archive.php and so on styling.

Sage Theme Sidebars – How to set sidebars up properly in Sage

Sage Theme Sidebars are WordPress sidebars set up DRY using the theme’s wrapper. Basically sidebars done the right way. You will need to add the code to register a new sidebar and add the code to the theme file where you want the sidebar to be loaded. Just like you would normally add a sidebar to a regular theme. There are however a few things you need to look at. These include:

  • the location to add the various code snippets,
  • namespaces and the
  • Sage specific loading and hiding of the general sidebar.

OK let’s dive into the matter and take it a step at the time.

Registering Sidebars in Sage

In your theme folder under theme/lib you can find the file setup.php. It combines what used to be called config.php and init.php. It basically is a config file containing code to add thumbnail sizes, sidebar, menus, theme assets as well as some tweaks when you use soil. Inside this file you will find this piece of code:

function widgets_init() {

 'name' => __('Primary', 'sage'),
 'id' => 'sidebar-primary',
 'before_widget' => '<section class="widget %1$s %2$s">',
 'after_widget' => '</section>',
 'before_title' => '<h3>',
 'after_title' => '</h3>'
 'name' => __('Footer', 'sage'),
 'id' => 'sidebar-footer',
 'before_widget' => '<section class="widget %1$s %2$s">',
 'after_widget' => '</section>',
 'before_title' => '<h3>',
 'after_title' => '</h3>'
add_action('widgets_init', __NAMESPACE__ . '\\widgets_init')

This is where Sage registers its sidebars. Here you could copy the code of one of the Sage theme sidebars and customize it to your liking. The action to add these sidebars uses coding you may not be familiar with as Sage uses namespaces which are available since PHP 5.3. Really useful to organize code the way you organize files in folders.

Loading Setup File

Also, as you may have noticed, the sidebars are not added to functions.php, but to a separate file. In functions.php this file is loaded with the following array:

$sage_includes = [
 'lib/assets.php', // Scripts and stylesheets
 'lib/extras.php', // Custom functions 
 'lib/media.php', // Media functions
 'lib/setup.php', // Theme setup
 'lib/titles.php', // Page titles
 'lib/wrapper.php', // Theme wrapper class
 'lib/customizer.php', // Theme customizer 
 'lib/widget.php' // Theme customizer

Sage does it this way to not overload the functions.php with all code and organize all more logically. And I like it. Otherwise I would not be using this awesome starters theme in the first place.

NB widgets_init() might be confusing here as it is when you start out with sidebars or asides. But sidebars load widgets after all so that is the function we need and action we use to load all the registered sidebars.

Displaying the General Sidebars

Displaying the sidebars is just like with any other sidebar. You could use the code:

<?php if ( dynamic_sidebar('example_widget_area_name') ) : else : endif; ?>

in any theme file for example. However, Sage does apply the DRY principle and does not like to repeat itself. So it uses base.php to load all code that is used cross files to load template files with or without a general sidebar (vertical one). Here a snippet from that bare file:

<div class="wrap container" role="document">
 <div class="content row">
 <main class="main">
 <?php include Wrapper\template_path(); ?>
 </main><!-- /.main -->
 <?php if (Setup\display_sidebar()) : ?>
 <aside class="sidebar">
 <?php include Wrapper\sidebar_path(); ?>
 </aside><!-- /.sidebar -->
 <?php endif; ?>
 </div><!-- /.content -->
 </div><!-- /.wrap -->

As you can see it determines whether or not the sidebar should be loaded. This being the general sidebar.

Sidebars File

And then the base.php file  load the sidebars from templates/sidebar.php. It does this using the sidebar_path() function. That function is inside lib.wrapper.php and states:

function sidebar_path() {
 return new SageWrapping('templates/sidebar.php');

This loads a new instance of SageWrapping with the sidebar file. I could write a whole section on the wrapper, but I won’t here to keep this article within bounds. More on the Sage wrapper here. In that file sidebar.php you will initially find only:

<?php dynamic_sidebar('sidebar-primary'); ?>

This is the main sidebar that is loaded selectively using the soon to be mentioned display_sidebar() function. One for my theme in progress has:

 <?php if (!is_woocommerce()) : ?>
 <?php dynamic_sidebar('sidebar-primary'); ?>
 <?php endif; ?> 
 <?php if (taxonomy_exists('product_cat')) : ?>
 <?php dynamic_sidebar('shop-main'); ?>
 <?php endif; ?>

As you can see I added some WooCommerce specific code for loading Sage theme sidebars for the shop part of things.

Hiding the Main Sidebar

Sage has an added function / filter you can easily use to show or hide the general sidebar that comes with the theme. It is inside the file lib/setup.php:

 * Determine which pages should NOT display the sidebar
function display_sidebar() {
 static $display;
 isset($display) || $display = !in_array(true, [
 // The sidebar will NOT be displayed if ANY of the following return true.
 // @link
 return apply_filters('sage/display_sidebar', $display);

You just add the WordPress conditional tag of the page, post, custom post type you do not want to see loaded with a sidebar et voila!

Under extras.php you can see a class is added when the above mentioned array does not contain the conditional posts/pages/custom post types:

 // Add class if sidebar is active
 if (Setup\display_sidebar()) {
 $classes[] = 'sidebar-primary';

This as most pages either have a sidebar or aside or do not. Again, this is not relevant for footer or header sidebars or special sidebar areas.

Other Widgetized Areas

Other sidebars that are not content sidebars or asides loaded left or right of the main content are loaded in template files like they would normally be. They are normally added to the template parts where they should be loaded like the footer sidebar which is loaded from a template part using the following snippet in the base.php file:


This as they will not be part of the display_sidebars function as they are not content asides. In the footer template_part you will find:

<footer class="content-info">
 <div class="container">
 <?php dynamic_sidebar('sidebar-footer'); ?>

which loads the sidebar-footer. For most of our themes we have at least four footer widget areas or sidebars.


Well, that’s it. Now you know how Sage loads content sidebars or asides and other widgetized areas. You also know a bit more about the Sage theme wrapper used to keep things DRY. This to make sure we do not repeat ourselves. In the upcoming posts I will talk some more about the wrapper as well as creating template parts and general Sage theme development.

Sage Theme Deployment

When you work on a Sage theme with Trellis for your server stack  the final step tends to be the deployment of the theme itself. The Sage theme does get pulled onto the server using Trellis, but the dist folder isn’t.

This folder dist is excluded. This is done using the .gitignore. This is done as it is considered better practice not to have your compiled CSS and scripts on the repository. And those files wind up in that directory. Therefore it is left out.

Sage Theme Deployment Preparation

I assume you already provisioned your server running

ansible-playbook server.yml -e env=production

and did the general deployment using:

./ production

This already installed the LEMP server and WordPress and the them to begin with. See more information on Trellis setups here. I also assume you either installed online or imported the database with all the content.

Compiling all Assets for Production

Then, to have all the assets do a:

gulp --production

This to compile all files and remove source maps. This so you have all necessary CSS and JavaScript files without the unnecessary stuff for development of the theme.

Uploading Dist

Now to upload this dist folder as well I recommend using scp and the admin user. From the theme folder locally execute this command using the terminal:

scp -r dist/

This will copy over the dist folder with all its assets to your theme on the server. After that you will have no more 404s. The theme will load properly with all the CSS and JavaScript files present. Done!

NB Downside is that new deploys keep on removing the dist folder due to git setup.

Update: Better Alternative

Better alternative is to activate the build-before.yml hook file located in trellis/deploy-hooks.

# Placeholder `deploy_build_before` hook for building theme assets locally
# and then copying the files to the remote server
# Uncomment the lines below and replace `sage` with your theme folder:
- name: Run npm install
 command: npm install
 connection: local
 chdir: "{{ project.local_path }}/web/app/themes/sage"

- name: Run bower install
 command: bower install
 connection: local
 chdir: "{{ project.local_path }}/web/app/themes/sage"

- name: Run gulp
 command: gulp --production
 connection: local
 chdir: "{{ project.local_path }}/web/app/themes/sage"

- name: Copy project local files
 src: "{{ project.local_path }}/web/app/themes/sage/dist"
 dest: "{{ deploy_helper.new_release_path }}/web/app/themes/sage"
 group: no
 owner: no
 rsync_opts: --chmod=Du=rwx,--chmod=Dg=rx,--chmod=Do=rx,--chmod=Fu=rw,--chmod=Fg=r,--chmod=Fo=r

Modern WordPress Development with Roots

I have been working on and off as a web developer and mainly with WordPress for over 15 years. I worked with different frameworks such as Genesis, Thesis, Canvas and Divi. I also worked or dabbled in a few starters themes such as _s .  And often I have customized themes that customers found / bought on Themeforest. And as these options became less and less appealing to me I started to look for something else.  That choice has become Roots

Reasons why I am moving

The reasons why I am moving more and more of my projects to Roots with Bedrock as a modern WordPress stack, Ansible for provisioning and Sage as a starts theme are many. Allow me to go throught the most important ones.


To learn a new theme every time a client decides this is the theme for him or her is tiresome, tedious and NOT efficient.  It will require you to read new documentation, get familiar with different theme panels and coding techniques used by the developer in question.


Also there are many themes out there on Themeforest and other places that just have not been coded well.  Often themes inject CSS and javaScript in odd ways making it hard to override CSS using a child theme, lack options which require me to use lots of plugins, adding lots of files to the stack, making it hard to optimize the site. Sometimes pages are not even valid causing different displays in different browsers.

Something missing

Most of the time these starter themes or frameworks had something I liked, but they were always lacking something or were not rigid enough in the way they were set up to truly offer clients high end themes and get them these in the quickest way possible.


When you create your own code, or know a starter theme inside out you can provide your own support. When you use a theme that is supported by WordPress experts you have people to fall back too. Roots is such a warm and fuzzy place.

Roots to the rescue

When you create a product for a client don’t do something half-assed. Deliver quality and know your product. Good quality products attract good quality clients. And that is something we all want right?

Modern WordPress Development with Roots to the rescue! Roots is a all encompassing framework. Not a WordPress framework, but a place where people help you build better WordPress sites, become a better programmer and build sites faster. This by providing you with a Modern WordPress Stack: Bedrock, efficient provisioning system with Ansible and a kick ass starters theme called Sage.

This does not mean this will be easy. Learning a new work structure takes time, but believe me this is worth it. I am only a beginner, but I have been convinced and as a new enthusiast I will do my best to bring you over to the dark side too 😉

Modern WordPress Development with Roots

So what is modern WordPress Development with Roots? Well it is a package that helps you set up your local, staging and production environments,  put together a modern WordPress Stack AND provides you with a very effcient WordPress Starters Theme to deliver awesome WordPress sites your clients will love!


• Bedrock Modern WordPress Stack- Better folder structure, Dependency management with Composer, Easy WordPress configuration with environment specific files,  Environment variables with Dotenv, …


•  Bedrock-Ansible – Easy server provisioning with Ansible (Ubuntu 14.04, PHP 5.6 or HHVM, MariaDB), Local Vagrant Package, One-command deploys, WP CLI,..


• Sage Starters Theme – Roots Starters Theme managed with NPM asset builder, Bower for front end package management, Gulp for image optimization, concatenationan and minification and sass/less compiling and error checking, Browser Synch.

See next post(s) with more details on Bedrock, Bedrock Ansible and Sage:

  • Bedrock
  • Bedrock Ansible – To be added
  • Sage – To be added
  • Roots WordPress Stack Workshop