Have you ever had a website that needed a taxonomy archive? You want to provide the taxonomy and have your site show all posts that have any term from that taxonomy. Yes? No? WordPress does not provide the easiest way to achieve this goal.
There is no functional URL that provides all the posts for a given taxonomy. WordPress does provide term archives, but that is just one term at a time. For instance, if I’m blogging about mustards of the world, I don’t want to see just “yellow mustard” posts. I want that dijon! I want that honey mustard!
Let’s break for lunch; I’m stricken with hunger now.
*pause for food*
Now that we are back, bellies full, time to get back to business. If you are willing to be a little flexible, we can create a taxonomy archive. The way we are going to do that is by tagging each post with a generic “all” tag. We will need to accept that we will have a URL like http://pluginize-foodies.com/mustards/all . We could do much worse.
The Setup
For our fictional foodies blog, we are going to use the popular Custom Post Type UI plugin. With it, we will imagine we have one post type created: Condiments. We are also going to use it to create one taxonomy: Mustards.
Cook’s note: If you have more than one post type registered with Custom Post Type UI, you may need to amend spots using cptui_get_post_type_slugs().
I don’t want to manually “all” tag all my posts.
Neither do I. We are developers though, and WordPress is highly flexible, so we are going to have our CMS handle adding the “all” term for us:
/** * Add publish hooks to all our CPTUI post types. * * @since 1.0.0 */ function pluginize_post_type_listener() { $post_types = cptui_get_post_type_slugs(); // We default to just our CPTUI post types. foreach ( $post_types as $type ) { add_action( "publish_{$type}", 'pluginize_auto_add_taxonomy_terms_on_publish' ); } } add_action( 'init', 'pluginize_post_type_listener' );
/** * Automatically add our taxonomy term upon publish. * * Term needs to already be created in the database. * * @since 1.0.0 * * @param int $post_id Published post ID. */ function pluginize_auto_add_taxonomy_terms_on_publish( $post_id = 0 ) { if ( ! wp_is_post_revision( $post_id ) ) { $terms = [ 23 ]; // Set to the appropriate term ID for your "all" term. wp_set_object_terms( $post_id, $terms, 'mustards' ); // Set to your custom taxonomy slug. } }
Above we have two functions.
The first one is our “listener” plugin, and it runs on the `init` hook. With it, we are fetching our CPTUI post types (in this case, “condiments,”) and looping over each. In the loop, we are adding a callback function to the publish action hook.
For our example, it will be the `publish_condiments` hook. Every time we publish a new condiments post, the `pluginize_auto_add_taxonomy_terms_on_publish` function will run. That function will receive the now published post ID from WordPress.
For safety’s sake, we will make sure we do not receive a revision’s post ID. If not, we are going to set the term ID 23, which is our “all” term for the “mustards” taxonomy. Awesome, with this, we will never have to assign the “all” term ourselves. We just need to worry about the other mustard terms we have and hit publish.
Is it supper time yet? No? Okay, we will carry on.
All of my existing content needs our taxonomy term.
Chances are you have been posting for a while already. Plenty of your posts are going to be missing our “all” term. Also, we are too busy dreaming about our next meal with mustard. We do not have time to go through old posts and add the “all” term to them by hand. For this, we will use the following code to automate:
/** * Run queries for posts and assign our term to them. * * Conditionally able to run on just one-to-many post types, or all. * * @since 1.0.0 * * @param mixed $chosen Individual post type to potentially use. */ function pluginize_backfill_posts_by_post_type( $chosen = '' ) { // We only want to run this if there is a $_GET param of `?fill_posts=true` or `&fill_posts=true`. if ( empty( $_GET ) ) { return; } if ( ! isset( $_GET['fill_posts'] ) || 'true' !== $_GET['fill_posts'] ) { return; } if ( ! empty( $chosen ) && is_string( $chosen ) && post_type_exists( $chosen ) ) { // If we are passed a string as a chosen post type and it's valid. $post_types = [ $chosen ]; } elseif ( is_array( $chosen ) ) { // If we are passed an array of post types. $post_types = []; foreach ( $chosen as $item ) { if ( post_type_exists( $item ) ) { // If we have valid post types. $post_types[] = $item; } } } else { // Nothing at all passed in, default to all of them. $post_types = cptui_get_post_type_slugs(); } $args = [ 'post_type' => $post_types, 'post_status' => 'publish', 'fields' => 'ids', 'posts_per_page' => -1, ]; $backfill = new WP_Query( $args ); // We are only needing our post IDs, from published posts. while( $backfill->have_posts() ) { $backfill->the_post(); // We can reuse our function from above! pluginize_auto_add_taxonomy_terms_on_publish( get_the_ID() ); } } add_action( 'init', 'pluginize_backfill_posts_by_post_type' );
Astute developers will note that this function is a callback on the init hook, and that we have many checks that return early if not met. In order for this code to execute, you need to have either “?fill_posts=true” or “&fill_posts=true” in your URL. If we meet that condition, we’ll continue into the rest of the function.
A few things to note:
- We’re keeping the function flexible, in case we have another need for it.
- We’re accepting a value as a parameter to the function, so users can pass in a single post type or array of post types. If we receive a single post type, we check if the post type exists, and if so, add it to a new array. If we receive an array, we loop over each and check if each post type exists. If so, we’ll add each to the array. If we are not provided anything specific, we’ll grab all the post types from CPTUI.
Once we have our post types to work with, we do a query for all posts from all the types. At the same time, we only need the IDs of the posts, so we use the “fields” parameter to limit to just IDs. At this point, we have all the posts we need to update.
Hey, look, a familiar sandwich…er…function. We are able to re-use our callback attached to our publish_condiments action hook! All we need to do is loop over our WP_Query results and pass in the ID to our `pluginize_auto_add_taxonomy_terms_on_publish()` function, and it will handle the rest.
What if I do not want a custom taxonomy?
That is fine. We have you covered. Say you are already using categories instead of a custom taxonomy. With a little bit of extra code, we can include the condiments post type in the category archives:
/** * Add our post types to the query. * * @since 1.0.0 * * @param object $query WP_Query instance. */ function pluginize_amend_term_archive( $query ) { if ( is_admin() || ! $query->is_main_query() ) { return; } if ( ! $query->is_category() ) { return; } $query->set( 'post_type', array_merge( [ 'post' ], cptui_get_post_type_slugs() ) ); } add_action( 'pre_get_posts', 'pluginize_amend_term_archive' );
The code above uses the pre_get_posts hook to include our “condiments” post type in the query. This will make sure the category archives query for posts in the “post” and “condiments” post types.
Eat now?
At this point, we should have plenty of posts associated with our “all” term. All future posts should also be receiving it automatically. The only thing that remains is providing a link to the “all” term archive. When editing a chosen menu in Appearance > Menus, you should have the ability to add the “all” term as a menu item. Finally, we have a place for visitors to sit, pull up some food, and consume all of the content from your taxonomy.
Have any questions? Pop ’em in the comments below.
Extra! Download the code above in a convenient install-able plugin: pluginize_all_terms_taxonomy.zip
(We will also accept your best sandwich recipes.)
Editor’s Note: This is a previously published blog post that has been updated on February 20, 2024.
Best sandwich recipe: two slices of bread, peanut butter and jelly on one slice, baloney and cheese on the other. Combine into a sandwich and eat. Especially good for days when you can’t decide. 🙂
Also, excellent post. And the mustard example is actually clear and helpful. This is something that I’ll be able to use soon!
Who really is this post for, developers or anybody using WordPress (perhaps, because they’re not very development-savvy)? Well, if it’s for the latter, and such as use your CPT UI plugin, then I’m sorry to say you’ve done a very poor job of writing/teaching/helping. Could you review and reproduce? Thanks.
I’m open to objective feedback. Where can I improve in this case? I was testing the code before I started anything with the writing portion of the article. Is there a spot that I overlooked and perhaps didn’t test well enough? Explanations need worked on to aid those who aren’t quite as developer minded?
Thanks for your tutorial Michael! We’re going to use this as a roadmap and we’ll post any feedback.
Hi Michael! Thanks for writing this article. I need to believe there is a better way to accomplish this than by tagging everything.
It seems to me we could maybe do something like a custom template file that only applies to one URL, e.g.:
/mustards/
And which returns wp_list_categories? Or am I imagining things? 🙂
Thanks!
If you can get it working using something like that, I’m all ears. I presently stand by the point that it’s stuff that boils down to WordPress core and how it handles things.
Thanks, this is really helpful and I love how well you’ve covered the pros and cons of using taxonomies for this.
Another option is to list taxonomies in a table layout archive page. You can list all your posts or a custom post type in a table layout, with custom taxonomy filters and an extra column for each taxonomy. I’ve written a tutorial about how to do this here.