Don’t edit child themes – use grandchild themes!

Child themes are the easiest way to style WordPress sites. Rather than create a site from scratch, you can create a theme that shares most of its code and styling with a parent theme. The benefits of creating a child theme instead of editing the main theme is that if the main theme gets updates, none of your changes are lost.

But what if you need to modify a child theme? If you’re working with a Framework, like Genesis, it’s very likely you are already using a child theme – and editing that brings about all the disadvantages of editing a parent theme – you lose your changes if the theme is ever updated.

The solution is surprisingly simple. Instead of editing the child theme, create a grandchild theme. It’s very similar to creating a child theme, except you do it via a plugin. You add your custom functions to the plugin, just as you normally would in functions.php (though remember your plugin will be called much earlier than functions.php, so you’ll need to make sure that any code in your plugin only runs when an action is fired). Use the wp_register_style and wp_enqueue_style functions to add your CSS (by default a grandchild theme will add your CSS to the CSS of the parent or child theme, but you can change this behaviour by using the wp_dequeue_style function). Finally filter the result of the get_query_template function to control which PHP files are executed when a page is requested.

A typical grandchild theme might look like this:

/*
Plugin Name: Grandchild Theme
Plugin URI: https://www.wp-code.com/
Description: A WordPress Grandchild Theme (as a plugin)
Author: Mark Barnes
Version: 0.1
Author URI: https://www.wp-code.com/
*/

// These two lines ensure that your CSS is loaded alongside the parent or child theme's CSS
add_action('wp_head', 'wpc_theme_add_headers', 0);
add_action('init', 'wpc_theme_add_css');

// This filter replaces a complete file from the parent theme or child theme with your file (in this case the archive page).
// Whenever the archive is requested, it will use YOUR archive.php instead of that of the parent or child theme.
add_filter ('archive_template', create_function ('', 'return plugin_dir_path(__FILE__)."archive.php";'));

function wpc_theme_add_headers () {
	wp_enqueue_style('grandchild_style');
}

function wpc_theme_add_css() {
	$timestamp = @filemtime(plugin_dir_path(__FILE__).'/style.css');
	wp_register_style ('grandchild_style', plugins_url('style.css', __FILE__).'', array(), $timestamp);
}

// In the rest of your plugin, add your normal actions and filters, just as you would in functions.php in a child theme.

The two actions to add the CSS should be familiar to you (although you may also want to read this post). The filter is the important function. You can create as many of these as you wish, for different pages. Supported pages include:

  • index
  • 404
  • archive
  • author
  • category
  • tag
  • taxonomy
  • date
  • home
  • front_page
  • page
  • paged
  • search
  • single
  • attachment

If you want more control before deciding which template file to give to WordPress, create a proper function and insert your logic code into it:

add_filter ('archive_template', 'emw_archive_template', 10, 1);

function wpc_archive_template ($suggested_templates) {
	// Your logic here
	if ($test_passed)
		return $template_file;
	else
		return $alternative_template_file;
}

How to make sure your custom CSS is cached by the browser

If you’re writing a custom theme, you’ll want to make sure that the browser properly caches the CSS, so that it’s not requested each time. To solve this, you add a cache-control header to your .htaccess file:

<filesMatch "\.(css)$">
Header set Cache-Control "max-age=290304000, public"
</filesMatch>

But what if you subsequently change the CSS? If you’re not careful, users who have previously visited your site will still be using the old, cached file. The easy way to solve this is to use PHP’s filemtime to provide a version number to the wp_register_style function. This version number will be appended as a query parameter to your CSS request. When it changes, it will force the browser to re-request the file. The code goes something like this:

$timestamp = @filemtime(get_stylesheet_directory().'/style.css');
wp_register_style ('custom-style', get_stylesheet_directory_uri().'/style.css', array(), $timestamp);

If you’re not able to edit .htaccess to add the necessary headers, there is a simple solution. Rename style.css to style.php (and do the same in the code above). Now add the following lines to the top of the style.php file:

<?php
header ('Cache-Control: max-age=290304000, public');
header ('Expires: '.gmdate('D, d M Y H:i:s \G\M\T', time()+290304000));
header ('Content-type: text/css');
$date = @filemtime(__FILE__);
if ($date)
	header ('Last-Modified: '.gmdate('D, d M Y H:i:s \G\M\T', $date));
?>