Learning From The New Tutorials Section

An overview of creating the Tutorial Series Taxonomy Archives used in our prior version of the Pods website. Helpful for understanding how Taxonomy Archives work and how to customize them.

As you might have noticed I did some work on the tutorials section here on Pods.io recently to improve the organizing of the content. I added a new custom taxonomy, using Pods of course, to organize the tutorials–a custom post type–by user level and by subject. I then created a special template file for the first page of the tutorials section, as well as one for the tutorial_type archive in our theme, along with a handful of functions.

I wanted to share some highlights of the code I wrote, as it shows ways of implementing content from custom post types and custom taxonomies in the front-end. You should note that everything uses standard WordPress functions, and I never build a Pods object once. Even though these are Pods created content types, there is nothing about them that necessitates using special Pods functions, such as additional fields add to the taxonomy or table storage used with the custom post type.

A Little Background

The Pods Tutorials Section

The tutorial section of Pods.io was created last year to organize, in one place, all of the existing Pods tutorials created by the community, as well as those I was beginning to write. As you can imagine, the Pods website relies heavily on Pods. Back then we added a custom post type called tutorial. One of our developers, Phil Lewis, did some theme customizations to make a nice looking list of tutorials.

Since tutorial is a hierarchical post type, like pages, with parent and children, Pho; also wrote some cool code to change the read more button in the post previews to say “View Tutorial” or “View Series” depending on if it was the parent post for a tutorial series or an individual tutorials. Here is the code, that is used in a the template part for tutorial archive post previews that sets the button text:

global $post;
global $location;
global $content;
// Set the button text and title for single tutorials or series
$button_text = "View Tutorial";
$title = get_the_title();
$params = array(
'post_type' => 'tutorial',
'post_parent' => $post->ID
$children = get_posts( $params );
if( count( $children ) > 0 ) {
$button_text = "View Series";
$title = "Series: " . $title;

view raw
hosted with ❤ by GitHub

This simple bit of code uses get_post() to get the number of children a post in the tutorial post has, and if it is greater than zero, changes the $button_text variable.

The New Problem

The tutorials section was created to solve the problem that there was no central location to find all Pods tutorials. Over time, we created a new problem–too many tutorials in one place. Having too many tutorials, each covering a broad variety of topics, and serving many user levels made it hard to find the right tutorial. To solve this problem, I created the ‘tutorial_type’ taxonomy.

Implementing The New Taxonomy

One goal of this project was to replace the giant list of tutorials with a list of tutorial topics. I wanted to replace the first page of the tutorials archive, with this list, but since I still needed the full list of tutorials and only needed to make a few changes to it the template file that controlled it, I decided to use a template_include filter to change which template file is loaded, based on which page of in the tutorial archive was being shown.

Mark Jaquith has a great article on why template_include filters are a better choice then template_redirect filters that I highly recommend you read.

The callback function for this filter uses the query_var page and the conditional template tag is_post_type_archive() to determine if we are on the first page of the post type archive for tutorial, and if so it includes the template “tutorials-first-page.php”. Here is the complete code for the filter and its callback:

//include a diffrent template if it the first page of tutorials post archive
add_filter( 'template_include', 'pods_tutorials_page_one_temp_inc' );
function pods_tutorials_page_one_temp_inc( $original_template ) {
//get current page of posts
global $wp_query;
$page = $wp_query->query_vars['paged'];
if ( is_post_type_archive( 'tutorial' ) && $page == 0 ) {
return get_stylesheet_directory(). '/includes/tutorials-first-page.php';
} else {
return $original_template;

The tutorials first page template is a little odd as it is shown as the tutorial archive, but most of the data comes from the tutorial_type taxonomy. This is why I didn’t want to modify the template (archive.php) that would normally be used, as it was a completely different layout.

In my first page template, I generated the two lists of tutorial types using a function reliant on get_terms. At the bottom of the page is a list of tutorial series, which I used WP_Query to create.

The two lists of tutorial types are created via this function:

//list tutorials by topic for first page of tutorials CPT page
function pods_tutorials_list( $ids, $heading, $dashicon ) {
//include only terms whose ids were set in $ids param
$args = array( 'include' => $ids );
//get the terms matching the args
$terms = get_terms( 'tutorial_type', $args );
//add underscores to $heading in separate var for ul id
$name = str_replace(' ', '-', $heading);
echo '<h3>'.$heading.'</h3>';
echo '<ul id="tutorials-'.$name.'" class="tutorials-topics-list">';
//loop through terms
foreach ( $terms as $term ) {
//get link, description and name for each term.
$link = get_term_link( $term );
$description = term_description( $term->term_id, 'tutorial_type' );
$name = $term->name;
//create list item
echo '<li><div class="dashicons '.$dashicon.' tutorial-term-dashicon"></div><div class="tutorial-term"><a href="' . $link . '">' . $name . '</a>';
if ( !empty( $description ) ) {
echo '<br /><span class="tutorial-term-description">' . strip_tags( $description, '' ).'</span>';
echo '</div></li>';
} //endforeach
echo '</ul>';

view raw
hosted with ❤ by GitHub

I added this function to the theme’s functions.php to avoid having to duplicate the markup in the actual template file. This function creates an object with all terms of the taxonomy ‘tutorial_type’, whose ids match an array of ids from one of the function arguments $id using get_terms. I then did a foreach loop to create the list of term names ($term->name;) as links to the term archives (get_term_link( $term );) and the description of the term (term_description( $term->term_id, 'tutorial_type' );). The idea to use the term description came from Lindsay Branscombe, who is the author of many great Pods tutorials.

The last part of this page lists the series of tutorials. Specifically, I needed all posts in the tutorial custom post type that have child posts, since all tutorial series have a parent post, with the individual parts of the series as children of that post. There is no great way to do this. I ended up using WP_Query to get all tutorials, with parent posts and by looping through the results of that query, I created an array of IDs of their post parents. I then used array_unique() to remove the many duplicate IDs from that array, and used the result to create my output. Here is that function:

function pods_tutorial_series_lists() {
if ( ! ( $parents_unique = get_transient( 'pods_tutorials_series' ) ) ) {
//query for all posts with parents
$args = array(
'post_type' => 'tutorial',
'post_parent__not_in' => array( 0 ),
'posts_per_page' => –1,
$query = new WP_Query( $args );
//loop to create IDs of post parents
$parents = array();
while ( $query->have_posts() ) {
$post = get_post($query->post->id);
$parents[] = $post->post_parent;
//discard duplicates
$parents_unique = array_unique($parents);
set_transient( 'pods_tutorials_series', $parents_unique, WEEK_IN_SECONDS );
echo '<ul id="tutorial-series-list" class="tutorials-topics-list">';
//loop though parent posts
foreach ( $parents_unique as $series ) {
<li><span class="dashicons dashicons-list-view tutorial-term-dashicon tutorial-series-dashicon"></span><a href="<?php echo get_permalink($series ); ?>"> <?php echo get_the_title($series); ?></a></li>
echo '</ul>';

Note that I used the transients API to cache the final result in the database. This prevents WordPress from having to run the query and the loop to remove duplicate entries every time the page is loaded.

The Tutorial_Type Archive

I also created a copy of the theme’s archive.php template and called it taxonomy-tutorial_type.php and made a few changes to tweak how the tutorial types are shown. Understanding what template in a theme will be used for a content part is the most important part of working with Pods in your theme. This is why I always recommend that those new to working with custom post types and custom taxonomies in the front-end familiarize themselves with these great resources on the WordPress template hierarchy:

Other Fun Stuff

Preventing The Addition Of New Terms

Since only certain terms from the tutorial_type taxonomy are shown, I didn’t want anyone, myself included adding any new terms to the taxonomy. I often see Pods users solving this problem by creating a related field to a custom taxonomy, and updating the value of the taxonomy with a post_save action. That way only existing terms can be used.

That approach works, but it creates a whole new workflow. What I did was use WordPress’ “pre_insert_term” filter to throw an error whenever a new term is added to the taxonomy. Here is the filter and callback function for how I did it:

//prevent new terms from being added to the tutorial_type taxonomy
//Thanks to http://wordpress.stackexchange.com/a/112721/25300
add_action( 'pre_insert_term', 'pods_no_new_tutorial_types', 1, 2);
function pods_no_new_tutorial_types ($term, $taxonomy) {
if ('tutorial_type' === $taxonomy) {
return new WP_Error('term_addition_blocked', __('You cannot add terms to this taxonomy'));

view raw
hosted with ❤ by GitHub

Using Custom Rewrite Slugs

If you have a keen eye, you may notice that the tutorials section is located at pods.io/tutorials/ but the custom post type name is “tutorial.” This is because we set, under the advanced options tab in the Pods editor for the tutorial custom post type, a custom rewrite slug of “tutorials.” Also, when I created the custom taxonomy for organizing tutorials, I called it “tutorial_type,” but when our lead developer Scott was reviewing the redesign he pointed out that the URLs would look better with “tutorial-series,” so I created a custom rewrite slug for the taxonomy as well.

Let Us Know What You Think

As always, I’m looking for user feedback on how to improve our support resources. So let me know in the comments what you think about what we did and how we can improve. Also, if you have a tutorial of your own to contribute, click here to learn how.

Leave a Comment