23.7 Taxonomy Weights: Ordering Terms
Right, so you’ve got your categories and tags all set up. You’ve dutifully sorted your content into beautiful, logical buckets. Now you want to slap them on your website’s sidebar in a “Popular Topics” list, and… they’re in alphabetical order. Alphabetical! The default setting for when you have no opinion. It’s the digital equivalent of shrugging and saying “I dunno, whatever.” For a list of “Popular Topics,” this is, to use the technical term, completely useless. You don’t want “Antiques” before “Zeppelins” just because the alphabet says so; you want the terms you’ve used the most—the heavy hitters—to appear first.
This is where the concept of taxonomy term weight comes in. It’s not a built-in WordPress property like it is in Drupal; it’s a pattern, a way of thinking. We’re going to manually assign importance to our terms, most often by using their post count, and then order them by that value. It’s one of those things that separates a site that just works from a site that works for you.
The Core Idea: Querying by Count
The heart of this operation is the wp_term_query arguments, specifically orderby=count and order=DESC. This tells WordPress, “Hey, go get my terms, but sort them by how many posts they’re attached to, and give me the busiest ones first.” It’s straightforward and covers about 90% of use cases.
Let’s say you want to list your top 10 most-used tags. Your code would look something like this:
$tags = get_terms( array(
'taxonomy' => 'post_tag',
'orderby' => 'count',
'order' => 'DESC',
'number' => 10,
'hide_empty' => true, // This is crucial unless you want to showcase your failures
) );
if ( ! empty( $tags ) && ! is_wp_error( $tags ) ) {
echo '<ul>';
foreach ( $tags as $tag ) {
echo '<li><a href="' . get_term_link( $tag ) . '">' . $tag->name . ' (' . $tag->count . ')</a></li>';
}
echo '</ul>';
}
See that hide_empty => true? That’s your best friend here. There’s nothing more professionally embarrassing than presenting a “Popular Topics” list that includes a term used once in 2017 and never again. It’s like introducing your friend group and pointing to an empty chair.
When Count Isn’t Enough: The Manual Override
Sometimes, raw popularity is a terrible metric. Maybe you have a vital “About Us” category that only has one post, but you absolutely need it to appear first in a navigation menu. Or perhaps “Unicorn Care” is your flagship topic, but you just launched it and it only has two posts. Alphabetical order would bury it; count-based order would bury it deeper.
This is where you need to get clever and create a manual weighting system. The most common way is to use a term meta field. Let’s say we add a term_weight field to our terms using a plugin like Advanced Custom Fields or by manually managing term meta.
First, you’d assign a high weight (e.g., 100) to your “About Us” term. Then, you’d modify your query to order by this custom meta value, with the count as a fallback for all the terms you couldn’t be bothered to manually weight.
$terms = get_terms( array(
'taxonomy' => 'category',
'orderby' => 'meta_value_num', // Order by a numeric meta value
'meta_key' => 'term_weight', // The key of our custom field
'order' => 'DESC',
'hide_empty' => false, // We might want to show our important but empty term!
'meta_query' => array(
array(
'key' => 'term_weight',
'type' => 'NUMERIC',
'compare' => 'EXISTS', // Include terms that have this meta key
),
),
) );
This query is more complex. It says, “Get me all categories that have a term_weight field, and sort them by that number.” The problem? It completely excludes any term that doesn’t have this meta key. Your “Unicorn Care” term, which you haven’t manually weighted yet, won’t show up at all. This is a classic “gotcha.”
The Grand Unified Ordering Theory
To create a truly robust system, you need a query that incorporates both manual weights and post counts, and gracefully falls back for terms without any manual weighting. This requires a more advanced approach, often using the posts_clauses or terms_clauses filters to write a custom SQL ORDER BY clause.
The logic we want is: “Order by the manual term_weight first (highest to lowest). For any terms that have the same weight or no weight, then order by the post count (highest to lowest).”
Achieving this purely with get_terms() arguments starts to get hacky. It’s often clearer to use a WP_Term_Query and a filter to modify the SQL. This is trench warfare, but it’s how you build something bulletproof.
// Add a filter to modify the term query's SQL clauses
add_filter( 'terms_clauses', 'modify_term_order_by_weight_and_count', 10, 3 );
function modify_term_order_by_weight_and_count( $clauses, $taxonomies, $args ) {
// Only modify queries for a specific taxonomy or context if needed
// if ( ! in_array( 'category', $taxonomies ) ) return $clauses;
global $wpdb;
// This is the gnarly part. We LEFT JOIN the termmeta table to get our weight,
// and then order by weight (treating NULL as 0) first, then by count.
$clauses['join'] .= " LEFT JOIN {$wpdb->termmeta} AS tm ON t.term_id = tm.term_id AND tm.meta_key = 'term_weight'";
$clauses['orderby'] = "COALESCE( CAST(tm.meta_value AS UNSIGNED), 0 ) DESC, tt.count DESC";
return $clauses;
}
// Now run your query
$terms = get_terms( array( 'taxonomy' => 'category' ) );
// Remove the filter immediately so we don't affect other queries
remove_filter( 'terms_clauses', 'modify_term_order_by_weight_and_count' );
This code is advanced, and I won’t sugarcoat it: it’s fragile. You’re writing raw SQL that interacts with WordPress’s core tables. You must remove the filter immediately after your query, or you’ll accidentally reorder every other term query on the page, leading to chaos and a very confusing debugging session. This is the kind of power that comes with great responsibility. Test it thoroughly, and for heaven’s sake, cache the results. You don’t want this running on every page load.
The takeaway? For most things, orderby=count is your friend. When you need more control, be prepared to get your hands dirty with meta fields and potentially custom SQL. It’s not always elegant, but it gets the job done—and done right.