7 Tips for Better WordPress Theme Development

I gave this talk at WordCamp Norway 2013. It covers several tips and tricks that will make you a better theme developer.

You can follow the slides and the notes/transcript below. If you have any questions or comments, feel free to ping me here or on Twitter, I’m always happy to help :)

1. get_template_part is Your Friend

The get_template_part function is used to include template files in your theme, but it’s not just a regular php include or require. It’s quite a unique way to include files because of its fallback model and child theme awareness. To understand that, let’s take look at some quick examples:

get_template_part( 'something' );

This will attempt to include something.php in our theme. However, if we’re running a child theme, get_template_part will look for something.php in our child theme first, and if that does not exist, it will fall back the parent theme’s something.php.

Here’s a more complex example with the optional second argument:

get_template_part( 'something', 'else' );

This will first look for a file named something-else.php and if that’s not found, it’ll look for something.php instead.

In a child theme environment it gets a little tricky. It will first look for something-else.php in our child theme, then something-else.php in our parent theme. If not found, it’ll look for something.php in our child theme, and finally, something.php our parent theme.

There are many great examples with post navigation, breadcrumbs, pagination, etc. Many themes, including Twenty Eleven and Twenty Twelve, use get_template_part to break down posts output based on their format:

get_template_part( 'content', get_post_format() );

If the post is a gallery post, this will include content-gallery.php. If it’s a quote, it will look for content-quote.php, and so on. And if we’re creating a child theme, we can easily override the output of our quotes by creating our own content-quote.php file.

To summarize that, get_template_part is a great way to break your theme down into smaller chunks, and because of its fallback model and child theme awareness, it will add some flexibility to your parent theme.

Resources:

2. Enqueue Scripts and Styles

When working with Javascript or CSS files in WordPress, you should never hard-code them in your header.php, but instead use wp_enqueue_script and wp_enqueue_style, and let WordPress take care of the rest. You should also make sure you do that within the wp_enqueue_scripts action and never within the global scope.

function my_scripts_and_styles() {
    wp_enqueue_script( 'my-script', ... );
    wp_enqueue_style( 'my-style', ... );
}
add_action( 'wp_enqueue_scripts', 'my_scripts_and_styles' );

Following these simple rules will make sure that your scripts and styles are loaded properly, and in the correct order. It’ll also make sure that such scripts and styles could easily be removed by a child theme or a plugin with wp_dequeue_script and wp_dequeue_style.

function remove_my_scripts_and_styles() {
    wp_dequeue_script( 'my-script' );
    wp_dequeue_style( 'my-style' );
}
add_action( 'wp_enqueue_scripts', 'remove_my_scripts_and_styles', 11 );

This is especially important when in the context of a child theme, because a child theme might not want to use all of its parent’s scripts and styles, and having a way to remove or replace them without overriding the whole header.php file makes your parent theme easier to work with.

A nice bonus of using these enqueue functions is the automatic dependancy resolution. For example, if your javascript file uses the jQuery and jQuery UI libraries, you simply tell wp_enqueue_script about it, and WordPress will take care of loading these dependancies before loading your script.

wp_enqueue_script( 'my-script',
    get_template_directory_uri() . 'script.js',
    array( 'jquery', 'jquery-ui-core' )
);

Finally, it’s worth noting the difference between the two functions used to get the current theme’s URL, which are often used when enqueuing scripts and styles.

get_template_directory_uri(); // parent theme
get_stylesheet_directory_uri(); // child theme

We’ve got the get_template_directory_uri which returns the URL for the parent theme, and get_stylesheet_directory_uri which returns the one for the child theme. When we’re not running a child theme, both functions will return the same result, but it’s easy enough to spot an error, just by switching to a child theme and seeing if things break.

Resources:

3. Customizer > Theme Options

Many WordPress themes come bundled with a theme options page, to customize the appearance of the theme, but in version 3.4, WordPress introduced the Theme Customizer. It’s a great end user experience, and easy enough to incorporate in your themes, using its very simple and flexible API.

The Customizer has pre-built controls for things like text, checkboxes, radio groups and dropdown selects, as well as more complex controls such as the color picker and the image uploader. Any of these controls can easily be extended to fit your needs, which you can then reuse in any of your projects.

While themes can implement the same settings in both, the Customizer and a Theme Options page, I think that developers should stop doing that, and focus only on the Customizer, because it’s just so much easier for both the users and the developers. After all, it’s been made to customize themes.

Resources:

4. Add an Editor Style

I’m not a big fan of the visual editor in WordPress, and I think one of the reasons is that most WordPress themes don’t add an editor style. Out of over 1,600 themes in the WordPress.org directory, only 200 had an editor style. So what’s the point of using the visual editor, if what I see looks nothing like what I get?

It’s very easy to add an editor style to your theme. All you have to do is call add_editor_style during your theme setup in functions.php, and add an editor-style.css file to your theme. You don’t have to write the whole CSS from scratch, in fact, 99% of it is going to be copy-pasted from your original stylesheet.

add_editor_style();

Themes with an editor style look prettier on the inside, and spare users a whole lot of frustration and time, which they otherwise spend previewing their posts and aligning their images before publishing.

Resources:

5. Avoid Using query_posts

This is my favorite. We have three ways to query in WordPress: we can use the WP_Query class directly, we can use the get_posts function, or the query_posts function. Each of these methods performs a secondary query to the WordPress database, but query_posts does this in a very special way, where one would think they’re altering the main query.

That’s a wrong assumption and what happens in reality, is that query_posts does not alter, but replaces the main query with a new one and if you don’t know what you’re doing, you might end up breaking things like pagination, titles, etc.

Here’s my rule of thumb: if you really want a secondary query and not alter the main query, use WP_Query or get_posts, whichever you prefer. They don’t have that query_posts magic happening under the hood, which makes them safer to use.

If you want to modify the main query, you have two options. The pre_get_posts action, and the less popular request filter.

I asked on Twitter what were the most common use cases for query_posts in WordPress, and the top three were featured posts, custom post types on the front end, and minor things like exclude a category from the stream.

The last two are pretty simple with the pre_get_posts action:

function my_pre_get_posts( $query ) {
    if ( ! $query->is_main_query() || is_admin() )
        return;

    $query->set( 'post_type', array( 'post', 'book' );
    $query->set( 'tag__not_in', array( 2, 6 ) );
}
add_action( 'pre_get_posts', 'my_pre_get_posts' );

The pre_get_posts action is fired for each and every query, including our main query, but also things like navigation menus and any custom queries we run, so first, we make sure that the query we’re working with is the main query, and let’s also not break the output in our admin panel.

Then we can use the set method to set any query variables, like post type, or the categories we’d like to exclude.

Featured posts are slightly more complicated, because you need a secondary loop, as well as a filter on the main loop. There are many different approaches for featured posts, and one of my favorite is to use sticky posts.

Let’s assume our theme has an area for five featured posts. We’ll need a secondary query to grab five posts which are set to sticky, let’s wrap that into a function.

function my_get_featured_posts() {
    $sticky = get_option( 'sticky_posts' );

    if ( empty( $sticky ) )
        return new WP_Query();

    $args = array(
        'posts_per_page' => 5,
        'post__in' => $sticky,
    );

    return new WP_Query( $args );
}

This is quite simple, we read the sticky_posts option which is an array of sticky post IDs, and we use that array in the post__in argument to the new WP_Query, which we return.

Now, wherever our featured content area is, we’ll just use this new function to query for featured posts:

$featured_posts = my_get_featured_posts();
while ( $featured_posts->have_posts() ) {
    $featured_posts->the_post();

    the_title();
    the_excerpt();
}
wp_reset_postdata();

Since we’ll most likely want our featured posts on our home page only, we can limit that with simple conditional statements, like this:

if ( is_home() && ! is_paged() ) {
    $featured_posts = my_get_featured_posts();
    while ( $featured_posts->have_posts() ) {
        $featured_posts->the_post();

        the_title();
        the_excerpt();
    }
    wp_reset_postdata();
}

Now comes the tricky part. To avoid showing duplicate posts, we will want to exclude these featured posts from our main query. As we have already seen, we can do this with the pre_get_posts action:

function my_pre_get_posts( $query ) {
    if ( ! $query->is_main_query() )
        return;

    if ( ! $query->is_home() || is_admin() )
        return;

    $exclude_ids = array();
    $featured_posts = my_get_featured_posts();

    if ( $featured_posts->have_posts() )
        foreach ( $featured_posts->posts as $post )
            $exclude_ids[] = $post->ID;

    $query->set( 'post__not_in', $exclude_ids );
    $query->set( 'ignore_sticky_posts', true );
}
add_action( 'pre_get_posts', 'my_pre_get_posts' );

First of all we make sure we’re working with the main query and the one that is run on our home page, and not in the admin. Then we use our function to get featured posts and put their IDs in an array. We use that array to exclude these posts from the query, and finally set the ignore_sticky_posts argument to true, which will prevent WP_Query from prepending any sticky posts to our query.

Again, using sticky posts is just one of the ways to implement featured posts in themes, but the principle is similar in other methods as well.

Anyways, back to my point. Don’t use query_posts, because query_posts is evil. Use the pre_get_posts action or the request filter to modify the main query, and WP_Query or get_posts for secondary queries.

Resources:

6. Never Start from Scratch

If you’ve ever made a new WordPress theme from an empty folder, you’ll know it’s a pain. There are several ways to speed up theme development, including child themes, theme frameworks and starter themes.

Child themes are useful when you want to make some minor changes to an existing theme, like add a sideboard, or a menu, change the colors and so on. Such changes can be made in just a few lines of code, especially if the parent theme is coded in a proper way. If you’re looking for a good parent theme, my advice is to start with Twenty Eleven and Twenty Twelve.

Theme frameworks are often large libraries of custom functions, actions and filters, with their own developer docs and communities. They’re mostly used to create new themes, though some frameworks come in a form of a parent theme. There’s often a steep learning curve to get started with a framework, even for highly skilled WordPress developers.

Starter themes on the other hand, are usually lightweight themes with minor styling or no styles at all. These are meant to be copied, renamed and built into full-feature themes that are completely independent, unlike child themes or themes built with frameworks. It is also common to fork a starter theme to adapt it to your own needs, thus create your own starter theme, which you can use as a base for all your future projects. If you’re looking for a good starter theme, check out Underscores.

To wrap up, don’t reinvent the wheel. Code reuse is a good programming practice. Try out some of these options, and see what works best for you.

7. Know Your Tools

Here’s a short list of some of the tools I use for better theme development.

Theme Check is a plugin that runs several thousand tests against a theme and tells you what’s wrong with it. If you’re planning to submit your theme to the WordPress.org directory, passing Theme Check is required.

Theme Unit Test is an XML file with a lot of dummy data, useful to see how your theme handles the different types of content, including images, post formats, comments, embeds and more. This is also required for WordPress.org.

Log Deprecated Notices is a plugin that will let you know whether your theme or plugin is using any deprecated functions or arguments.

Debug Bar, Debug Bar Extender, Debug Bar Console – these three plugins are a must for all WordPress developers. They are like like Firebug for WordPress. You can inspect queries, cache and other helpful debugging info, and with the Console plugin, you can have a PHP and MySQL console right in your browser.

Monster Widget – this neat little plugin allows you to place a single widget that will render all the WordPress core widgets. It’s like the theme unit test, only for your sidebars.

WordPress Beta Tester is a plugin that allows you to run the development version of WordPress and make sure your themes are compatible with the next WordPress release.

Core Control is a plugin plugin that allows you to log and debug HTTP requests, cron tasks and more.


That’s about it. If you have any questions, poke me.

45 thoughts on “7 Tips for Better WordPress Theme Development

  1. I would like to say that you put here very nessecery tips. And #7 with the tools knowing is one of the most important, as for me. The site owner should to know as many as it possible about his/her website.

  2. In the trick of getting featured post ids, we can use function wp_list_pluck to avoid “for” loop.

    Also, at wordpress.stackexchange.com, there’s a very detailed answer to the question asked about the difference between query_posts, WP_Query and get_posts (I don’t remember the exact link, but it’s easy to find there) which is a very good addition to this post.

  3. At first I was thinking that editor styles are cool. But many end users change fonts and colors in child theme and they end up wondering why there is still original fonts and colors in edit screen.

    In overall this is great post. Thanks!

    • Hey Sami, thanks for your comment! If child themes change colors and fonts, they should probably take the same changes to the editor styles too, but even if they don’t, I think that wondering why the original color is still there, is much better than wondering why it all looks totally different :) Thanks for your feedback and have a great weekend!

    • You got me convinced. It’s pretty easy to copy editor-style.css to child theme and end user can make their own changes if they want.

  4. What a great presentation (and write-up)!

    I really hope that more and more people will adopt these best practices in favor of the bad ones, which unfortunately caught up.

    I’ve been reviewing today some pages on the Codex, especially for beginners, and I’ve spotted a handful code examples using query_posts(). Hopefully I’ll be able to work on these in the next week or so.

    • Hey keesiemeijer, thanks for your comment! Indeed, the pagination functions (and plugins) rely on the $wp_query global, though it’s easy enough to manually peek into a custom WP_Query object. I’d add an optional argument to such pagination functions that could take a custom WP_Query object to figure the pages out, and then we can deprecate query_posts :) win-win!

    • I agree, unfortunately there are not that many WordPress pagination functions that accept a WP_Query object as a parameter. To show a list of posts on a Page template you can’t query by using pre_get_posts (or not that I know of). That leaves us with new WP_Query in the template itself which doesn’t work for pagination. I shall take a look into the ‘request’ filter to see if I can finally get rid of query_posts and use pagination :)

    • Altering the main query on a page request is not a good idea, so you should definitely use a secondary query :) the request filter is not too different from the pre_get_posts action — it’s fired earlier, it’s fired for the main query only, and it’s query arguments are not parsed yet.

      unfortunately there are not that many WordPress pagination functions that accept a WP_Query object

      Afaik there are no WordPress pagination functions that would accept a query object, which is why I said it’d be cool to add an optional argument, and have plugin authors follow along. In any case, it’s not a big deal. As long as you know what you’re doing, still using the main query and then resetting the query when you’re done, etc., you’re good to go :)

    • Thanks again for the explanation and this post. The only function I know of is paginate_links(). You can pass the the max_num_pages property from your custom query for the $total parameter. it would Indeed be very cool if other functions would allow us to do that also :-)

    • Last reply from me, I promise ;) I see now that you can set $max_pages for the next_posts_link and previous_posts_link as well. All query_posts have finally been removed. Yay.

    • You’re right, I was looking at get_previous_posts_link which uses the $paged global only. The get_next_posts_link however, does accept an optional $max_pages argument, which lets us short-circuit the check in the $wp_query global, so yes, that’s an easy alternative to pagination in page templates without query_posts. However, plugins that use $wp_query still remain…

      I just browsed through a couple and looks like WP-PageNavi allows you to pass additional arguments to the wp_pagenavi function, one of which is the query, which defaults to the $wp_query global. So you don’t needs query_posts for WP-PageNavi to work on page templates with custom loops. I hope there are others following the same or similar practices.

  5. Other than these I would like to share some more tips which includes shorthand css in your theme’s stylesheet and style wordPress Image Captions. Every good wordPress theme should include default styles for image captions and to style the default caption, you can use the .wp-caption class in your styles.css.

  6. When adding editor_style.css and working with a theme that uses Google Fonts, does it make sense to load fonts in post edit screen as well? I think Twenty Twelve doesn’t do it with Open Sans.

    In my opinion, if you can find something similar enough that should be OK, as long as font sizes and colors are there. And a section in readme file that explains it’s not supposed to look exactly like “real post”.

    • I was wondering kind of the same thing. I’m not sure but if you gonna do it, why not doing it as good as you can. And what if I use @font-face in my headings for example. How should I use that in editor-style.css.

    • You can @import the webfont stylesheet at the top of your editor-style.css file.

      Also if visit the Google Webfont stylesheet URL you’ll see the @font-face of your fonts, hence you can copy these rules and paste at the top of your editor-style.css file.

    • What if theme has options that allow users to select which Google Font is in use? Importing them all is out of the question, for good reason, I guess it would be best to just not import Google Fonts then?

    • Another thing I ran into — since I have Customizer controls for body font and heading font, I needed to pass classes to Tiny MCE, turns out it was easy, using tiny_mce_before_init and teeny_mce_before_init filters.

      Now, combining this with mce_css, I can load Google Fonts only if needed, and only the ones I need to, but have styles for any font option in editor style:

      .body-lato { … }
      .body-helvetica { … }
      .heading-open-sans { … }
      etc.

      Thanks, you got me going with this!

      I know I’m responding to my own comment, but this thread got too deep :)

    • I haven’t tried it myself yet, but I don’t really see a problem using a Google Web Font or any other embedded font with TinyMCE. If it doesn’t work, we should be using the closest available web-safe font with the correct size, line-height and letter-spacing. Thanks for bringing this up, will give it a shot!

  7. By webfont stylesheet, I meant the one provided by Google when you choose the fonts you want to use on your website, e.g. //fonts.googleapis.com/css?family=Open+Sans:300italic,400italic,600italic,700italic,800italic,400,300,600,700,800 for Open Sans

    • Yes but I’m not using google web fonts either. Your both suggestions are great but that’s not the case I’m trying to do. I have @font-face in my style.css. Maybe I just add it also in separate stylesheet and load it only in admin.

Comments are closed.