I’ve been developing WordPress themes and plugins for the past 6 years, and in that time, I’ve learned a lot about how to efficiently centralize and organize WordPress theme files.

For a long time, I thought that using Template Files was the right way to organize my WordPress theme– just whip up a post-type-specific template-file, like archive-{post_type}.php. Following the Template Hierarchy led me to a very decentralized setup, which can be inflexible and difficult to maintain.

The Danger of Relying on WordPress Template Conditionals

For example, on just about every project I have a file called header-section.php , where I put a section title, breadcrumbs, and maybe a background photo.

Normally, whenever I needed to change something in section header out based on the post type, I relied on Template Conditionals.

So if I wanted to change out the section title on the events archive, I’d write a template conditional like is_post_type_archive( ‘events' ) . But if I’ve got a taxonomy archive for events too, I’d have to couple that on there as well, like this: is_post_type_archive( ‘event' ) || is_tax( ‘event-category' ) .

This eventually led me to create themes that were organized entirely around the markup, not the layout. Maintenance and minor updates were a challenge for other developers: changing out the section title “Events” for “Calendar” is simple, but only if you already know that header-section.php exists.

This is a basic example, but imagine this organizational pattern spread across a site with 13 custom post types. It eventually got to the point where I could no longer be sure that the code we were writing was 100% consistent and 100% reliable.

Other Issues with Markup-First Organization

I also ran into trouble with code repetition; that is, repeating other “supporting” code, like centered divs. It got to the point where I wasn’t sure which layouts had what code, so I started layering more code on top of existing code, which led to a bizarre patchwork of code held together by prayers and ‘if’ statements.

Using a Layout-First Approach

I eventually got fed up with my overuse of WordPress Template Conditional tags, and started thinking about what I could do to only use 1 Template Conditional of each type per site.

Using a Theme Wrapper to Centralize Supporting Markup

First, I went an found a Theme Wrapper technique from the Sage theme by Roots: this allowed me to centralize all of my “supporting” markup into one file that every layout will use. This includes structural divs that us developers normally just stuff into header.php or footer.php , like a .main div, a .content div, and so on. This way, every layout on the site will use the same basic structure.

Using Custom Actions to Insert Layout-Specific Code

Next, I added custom actions in between every div in base.php , as well as actions in the WordPress Loop in `index.php`. For example, I have:

before_webpage_div

before_wrapper_div

before_main_div

before_content_div

before_index

before_loop

before_content

after_content

after_loop

after_index

after_content_div

after_main_div

after_wrapper_div

after_webpage_div

That’s a lot, for sure, but the important thing to note is that the custom actions are based on the structure. So if the structure changes, so do the actions.

The point of building custom actions into the theme is so that we can output any markup we need to at specific areas throughout different layouts and views. For example, I can:

Hook a Google Tag Manager script to output after the opening body tag using the before_webpage_div action. Add pagination at the bottom of archive pages using the after_loop action. Remove the sidebar action normally hooked into after_content_div on specific layouts by using remove_action.

We don’t have this same sort of flexibility with Template Conditionals: the choice is to hardcode or hack around.

Tying Things Together With Layout Classes

Once I started using theme actions and filters instead of layout-specific conditionals, I realized that the potential went much farther than just keeping my template files a bit cleaner. For instance, here’s what one of my layout files looked like when I was just getting started:

<?php add_action( 'before_wrapper_div', 'client_get_header_int', 20 ); function client_get_header_int() { if ( ! is_front_page() ) { get_template_part( 'partials/header-int' ); } } add_action( 'after_content_div', 'client_interior_get_sidebar', 20 ); function client_interior_get_sidebar() { if ( ! is_front_page() ) { get_sidebar(); } } );

Compare that with this layout-specific PHP class:

<?php namespace CLIENT; class Interior { public static function init() { add_action( 'wp', function () { if ( ! is_front_page() ) { self::hook_wordpress(); } } ); } public static function hook_wordpress() { add_action( 'before_wrapper_div', [ __CLASS__, 'get_header_int' ], 20 ); add_action( 'after_content_div', [ __CLASS__, 'get_sidebar' ], 20 ); } /** * Gets the interior header if we're not on the home page. */ public static function get_header_int() { get_template_part( 'partials/header-int' ); } /** * Gets the sidebar. */ public static function get_sidebar() { get_sidebar(); } } Interior::init();

The result is the same even though the way we get there is different. The advantages to using a PHP Class are:

No function namespacing required, namespace the class instead.

One layout conditional check per class, rather than one per function.

Cutting out inefficient code means we work faster. Apply this principle across a whole project rather than just one file, and you’ll see the benefits of it.

We went through a few hurdles in order to make it here. Here’s what you need to look out for:

How to Remove a WordPress Action from Another Class inside a Namespaced Class

In the hook_wordpress method, run this:

remove_action( 'tag_name', [ 'NAMESPACE\Other_Class_Name', 'function_to_remove' ], identical_priority );

This was by far the hardest thing for me to figure out. The docs on remove_action() aren’t that great, and while they address how to remove an action from a class, they don’t address how to remove an action from namespaced class.

Here are the requirements:

The layout classes must be static.

You have to match the tag, namespace, function name, and priority. So if you run add_action() with a priority of 20, remove_action() has to use 20 as well.

Keeping Layout Classes Focused Across Multiple Post Types

On a recent project, there were two post types: the default “post” post type, and a “news” post type. Each post type had a taxonomy too: “category” and “news-category.”

We used just about the same layout for each post type and taxonomy. In order to prevent myself from duplicating code, here are the layout classes I created:

Like this content? Meet Pagely.

NewsAndPost

NewsAndPostArchive

NewsAndPostSingular

News

NewsArchive

NewsSingular

Post

PostArchive

PostSingular

If I didn’t have the “news-and-post-” files, then I would have had to duplicate the code that applied to both post types. If I only had the “news-and-post-” files, I would have had to split layout conditionals, i.e. if news-singular, fire these 3 functions, if post-singular, fire these 2, and so on.

Create Custom Conditionals As Necessary

A quick note on conditional usage: for the NewsAndPost class, I created a custom conditional called is_news_or_post() so that I wouldn’t have to write out the whole thing twice.

<?php /** * is_news_or_post * * A custom boolean check for Hexadite so that we can test if we're on a news or post layout without repeating every single check every time we need to. * * @return bool */ function is_news_or_post() { $bool = false; if ( is_singular( 'post' ) ) { $bool = true; } if ( is_singular( 'news' ) ) { $bool = true; } if ( is_post_type_archive( 'post' ) ) { $bool = true; } if ( is_post_type_archive( 'news' ) ) { $bool = true; } if ( is_tax( 'news-category' ) ) { $bool = true; } if ( is_home() ) { $bool = true; } if ( is_category() ) { $bool = true; } if ( is_tag() ) { $bool = true; } return $bool; }

For the other, more specific layout classes, I used the conditional that made sense: is_post_type_archive( 'news' ) || is_tax( 'news-category' ) for NewsArchive, is_singular( 'news' ) for NewsSingular, and so on.

So it’s fine to use more than one conditional clause, but I would recommend against splitting conditionals. You want the layout class to stay as focused as possible.

In Conclusion

The combination of using a theme wrapper, custom actions and layout classes together helps me keep my codebase better structured and organized, even on large projects.

The ability to turn different parts of the layout off and on by using add_action or remove_action has been especially helpful as well.

If you’re having trouble wrangling a large number of Template Conditionals, I’d encourage you to give layout-first organization a try.