Complex WordPress IA 2: Custom Taxonomy Navigation

Complex WordPress Information Architecture

Part 1.

Before we dive in,  I forgot to include the code that actually shows the pages.  I won’t go in to it, because it’s covered here: in detail, but after we end our custom query loop, we want to insert another if statement to see if it’s paged or not.  If it’s paged, it will display our pagination.

What is a Custom Taxonomy?

A Custom Taxonomy is simply a grouping (taxonomy) that we, the developer, create, based on the needs of the project.  WP only comes with two default taxonomies, Categories and Tags.  While useful for illustrating the concept of what a taxonomy is, they can be very limiting and increase confusion on a site because a category or tag archive will pull every post and page that is tagged with that category or tag. Most of the time if you want an archive, you want to make it easier to find a specific type of post, and it gives us a lot more flexibility in organizing content.

In this lesson we’re going to:

  1. Create taxonomy-genre.php
  2. Create an in-page navigation element that lists all the genres and
  3. insert that element in to both templates.

First, creating templates is easy in WordPress.  We do the exact same thing we did last time. In our text-editor, open a new file, save as taxonomy-genre.php and we’re on our way.  We can do this because of the WordPress Template Hierarchy, which I’ve mentioned before.

The template hierarchy describes how the software finds the appropriate files to serve. If it can’t find the most specific file, it finds the next most specific, then the next, until it gets back to index.php.  Sidenote – when making your own WP theme or reviewing someone else’s work, templates should always be named according to the WP theme standards and coded to their spec. It’s not simply good practice, it’s manners, and those conventions exist for a reason.

Off of my soapbox.

Step 1: Custom Taxonomy Archive

In the case of custom post types the hierarchy is this:

  1. archive-customposttype-slug.php (where slug is the name of a specific item in that post)
  2. archive-customposttype.php
  3. archive.php

and the same logic applies to taxonomies. When in doubt, get more specific!  It helps for debugging.

We’ll make sure we include the header ( get_header() ) and the footer ( get_footer() ) and then copy in our code from archive-songs.php, because the same basic format will be used, just with slightly different logic.

Step 2: The Loop

For the loop, we’re going to add the $tax_query parameter.  Tax Query takes an array of arrays, and is in an array itself!  So our new custom query looks like:
// Define custom query parameters
$custom_query_args = array(
'post_type' => 'songs',
'tax_query' => array(
'taxonomy' => 'genre',
'field' => 'slug',
'terms' => $term->slug,


The query is getting posts of the post type songs, with a genre assigned to them, matching the slug of the term.

The logical question is, what is $term?

$term is a variable we need to set.  this should look a little familiar, but basically we’re setting $term to equal the slug of the page we’re on. Here’s that code, and we need to include this inside a php tag before our query.

$term = get_term_by( 'slug', get_query_var( 'term' ), get_query_var( 'taxonomy' ) );

This modifies our query to output only the Songs matching the Genre in the slug of the page that we’re looking at. Pretty cool huh?

Step 3: The tiny nav

Let’s take this one step further.  We can now add a navigation element to both templates using the same code to get all of the Genres so we can navigate between genres easily!  This is a win because primary site navigation has to reflect priorities and even important content like this may not be the most important 2-6 links.

Pro tip: the easiest way to make sense of navigation is to avoid overstuffing it in the first place.

I’m using a design pattern which you can find here. There is a tiny javascript dependency,  but the logic on it is pretty simple – basically, replace the full list with a select menu when the browser window is small enough.

But how to dynamically (that is, tell WordPress to automatically insert) our Genres in to the list?

We’re going to start by creating variables.  Inside of a php tag, paste in:

$taxonomy = 'genre';
$orderby = 'name';
$show_count = false;
$title = '';
$args = array(
'taxonomy' => $taxonomy,
'orderby' => $orderby,
'show_count' => $show_count,
'title_li' => $title,
'use_desc_for_title' => 0,
'current_category' => 0,

Then, we’re going to pass those arguments to a WordPress function called get_categories, and store that result in one final variable, $categories, like so:

$categories = get_categories($args)

Last, inside of of the nav element we’ve just made, we’ll make a foreach loop. For PHP newbs, the syntax can be a little challenging, but basically we’re telling WordPress that, for each Genre, output this specific chunk of html.  And, we’re also taking advantage of WordPress get_categories, because if it’s the current category, it will now append the class “current-cat” to the list item. This allows us to style the list to show a user what genre they are on.

foreach ( $categories as $category ) {

$term_id = $category->term_id;
$term_slug = $category->slug;
$term_name = $category->name;
$term_URL = get_category_link( $category->term_id );
$class = 'current-cat';
if ($term_slug == $term->slug) {
echo '<li class="cat-item cat-item-'. $term_id .' '. $class .'">'.'<a href="'.$term_URL .'">'. $term_name .'</a>'.'</li>';
} else {
echo '<li class="cat-item cat-item-'. $term_id .'">'.'<a href="'.$term_URL .'">'. $term_name .'</a>'.'</li>';

We’re wrapping each term name in the link to the term’s unique page.

This won’t include a link back to the songs archive, but that’s easy enough to include manually, like so:
<li><a href="/songs">ALL</a></li>

There you go!  To recap,  we now have the templates we need to show all of the songs, and all of the songs by a specific genre. Our custom taxonomy of genre is all set up to work automagically.

Next time, we’ll cover how to set up parent and child pages, and how to show sibling pages when you’re on a child for our discogrpahy and album pages.

How to make Complex WordPress IA Part 1

I’ve been heads down on a project at work that has had a number of reasonably interesting challenges, so I’m writing a series of posts to capture not just what I did but how I actually did it.

One of the major knocks on WordPress is that, because it was originally designed as blogging software, making comparatively complex information structures is more difficult than it could be with a different system. Still, there may be reasons a project needs to be both WordPress and have a complex architecture. As a developer, our overarching goal is to bend the software to the human needs, not try to bend people to the limits of software.

Without getting too in the weeds about how WP treats content, let’s show a hypothetical case instead.

Say we’re building a site for one of our favorite musicians.  Obviously, they want people to discover and understand their music better.  So we decide that the key content item on this site will be each Song they choose to put on the site.  We will need to have a custom post type called Songs. Each Song is part of an album, and each album has a genre.

Song -> Album

Song -> Genre.

Believe it or not, this is can be pretty complex to display.

Let’s say we wanted to show all of the Songs in a Catalog.  Then we want to sort the Catalog by Genre. Additionally, we’ll want to have a page that lists all our Albums (call it “Discography”) and each Album gets it’s own page. On each album page,  there’s a menu of all the albums.

Still following?

Here’s the basic IA structure we’re going to make.

Catalog -> Lists all Songs, Sorts by Genre (each Genre is it’s own url).

Genre -> Lists just the Songs in this Genre

Discography (parent) -> Lists all Albums (child)

Album (child) -> has menu listing all Albums (siblings).

Default WordPress IA has a really difficult time making this happen. The default WordPress IA can be summed up as: We make Menus out of Pages, and we display Posts on a Blog Page. It is not a coincidence that the templates WP looks for are home.php and index.php.  In our example we have posts, pages, parents, children, and if you’re not careful the logic could end up looking like spaghetti.

WP Ideal Site Map
This is the Ideal WP Information Architecture. You have 3 basic types of content, all related by built in functionality. 

Let’s start with giving our custom post type, Songs, a custom taxonomy.  I use the plugin Custom Post Type UI plugin, but the code to do it yourself is pretty straightforward.

Here’s the custom post type code:

add_action( 'init', 'create_posttype' );
function create_posttype() {
register_post_type( 'songs',
'labels' => array(
'name' => __( 'Songs' ),
'singular_name' => __( 'Song' )
'public' => true,
'has_archive' => true,
'rewrite' => array('slug' => 'songs'),

and here’s the custom taxonomy code:

function genre_init() {
// create a new taxonomy
'label' => __( 'Genre' ),
'rewrite' => array( 'slug' => 'genre' ),
add_action( 'init', 'genre_init' );

Now we’re cooking. Note this line especially:

 "has_archive => 'true'.

This allows us to create an archive template for songs, and tells WordPress to look for an archive template.

For this illustration we’re not going to create custom fields for each Song, but certainly we’d want to do things like:

  • list any awards this song won or was nominated for
  • list who produced, mixed, wrote, or performed on the song
  • show the lyrics
  • insert a soundcloud module so a user can listen to the song right there
  • insert a download link

and we can create all of those fields either on our own, or, even better, with the Advanced Custom Fields Pro plugin. There’s a free version that is excellent but A) I encourage you to support them, B) the pro version is even more useful and C) it’s a steal at the price.

Now we can make an archive template for all of the Songs, that is, our Catalog. We’ll make a new archive template,  which we’ll name


Like all WP templates we’ll start with get_header() and we’ll close the document with get_footer();. If you’re completely new to WP,  these tags help you repeat the head and closing parts of the page, which saves your typing and ensures that the right stylesheets and scripts load on every page.

Problem 1: how do we get all of the Songs?  In this example we’re going to create an unordered list (<ul></ul>) and each list item (<li></li>) will show a Song title, wrapped in a link (<a></a>) back to that song’s unique page.  This is the simple part.

WordPress queries the database using a function called, you guessed it, WP_query.

And this function accepts arguments – that is, we can tell WP_query what to, well, query the database for.

The way we do it is like so (inside a php tag):

// Define custom query parameters
$custom_query_args = array(
'post_type' => 'songs',

The key is we’re telling WordPress to only query the post type we’re after. There’s about a dozen different parameters we can configure with these arguments, and you should bookmark this link in the WordPress Codex. (For WP, the answer is almost always in the Codex).  Note that we are *not* going to use the ‘posts_per_page’ argument here. This is so we can use virtually the exact same code later, for our genre pages.

One thing you might not find is how to paginate these. This took me a little digging, but is actually quite simple.  We can write 1 line of php that will insert this parameter into our arguments, which will then be passed on to the query.

// Get current page and append to custom query parameters array
$custom_query_args['paged'] = get_query_var( 'paged' ) ? get_query_var( 'paged' ) : 1;

However, this only gets us halfway there.  We then must add additional code that sets the query object back to null.  The reason is simple – without this code,  WP will use the default query object.  Instead, we’re telling it to use our object, then null, and then finally reset everything so we don’t break the site.

Here’s that code:

// Pagination fix
$temp_query = $wp_query;
$wp_query = NULL;
$wp_query = $custom_query;
//then later, after we end the loop wp_reset_postdata(); // Reset main query object $wp_query = NULL; $wp_query = $temp_query;

and here is all of the code, together, so far for our




Voila!  You now have all of the Songs on an archive template.  This basic code will also help us make the Genre pages,  which will display all of the Songs in that Genre.

We’re right at about 900 words, a few php concepts, and lots to grok about WordPress IA.  In the next part of this series, we’ll tackle problem 2: How to let a user sort these songs by Genre from this template.



The Number One Question I Get

The Wrong Question

is “What will this website cost me?”

“How much will my website cost?” is a bad question.  A much, much better question is “How much value do you want to create with your website?”  Freelance web design varies a lot on cost (even in a local market like STL), and even more on the value they will deliver.  I’ve decided that whatever the project is, I’m going to focus on value for the business.

In fact, that’s where I’ll start a conversation with you about website cost. Because while there is a point of diminishing returns, there’s a pretty consistent relationship between investment and return. And, as we know, the returns do not have to be all that big to make a big difference in your company’s or organization’s bottom line.

So the first question is, How much value do you want to create? The next set of questions will be about the constraints. See, I could just tell you a fixed price here. I’d look at my family monthly budget, use the total, add a healthy bit of margin to it, and quote you a price. But the problem there is that price is only about me. It’s not about you and it’s a bit of a lie. Oh, sure I could justify it post hoc but I think that’s not what I want to be about.

I want to create value for you, which means, well, I need to get to know you. I’ll do my homework and come up with a few options for you, each of which will be suited to what your project goals are.  I try to provide three options no matter what. I want to blow your mind!

The Tiny Problem

That said, I do have some informal benchmarks I use to help me figure out what is possible. You simply can’t get a Michelin meal on a McDonald’s budget. And if your cousin will do it for only X amount, let them!  I won’t. There are options, and I’m happy to tell you what they are.

What if you have a small budget? First, let me say thank you for making a budget. That means you have your head on straight. Second, a small budget isn’t a bad thing, it just means different options and compromises.

Big Eyes

I’ve worked on big, complex projects and have absolutely added value there, but the truth of the matter is that Web Design is a team sport at that stage. There’s just too much to do. Ideally you’d want a team of 2-8 to work on projects over a certain dollar amount. This is exactly why I work at an agency –  I’ve got a team of people who are experts at what they do and together, we can deliver a lot more. It’s just not smart to try to take on something too big for me. I will let you know if something is too big for me to handle. And, honestly, if you’re reading this, you probably don’t have this particular problem.

Know Thyself

Getting the right answer to the question “What does a website cost” is an exercise in self examination.  You need to know how much you can afford, what your goals are and what you prioritize before you’ll get the best price for you.  The more self aware you come in to the project – or at least willing to let me get to know you – the better it will go.