Just like in my last post, I was asked to do something kind of interesting with a website recently. Here’s what needed to happen:

  1. Have multiple custom post types show up in the same feed on the homepage, and sort them by date (newest to oldest).
  2. Include a custom field on each of these post types that will allow a specific post to be “featured”. Posts with this field checked would appear first on the homepage.
  3. Include a custom field on each of these post types with will prevent a specific post from showing up at all (unless a user had a direct link to that specific post) on the homepage.
  4. All of these things need to be handled by the same query.

I won’t lie, this was an intimidating list and it threw me for a loop. In the end, it stumped me so a co-worker of mine was kind enough to help me tackle this beast. After a bunch of trial and error, here’s the solution he and I came up with.

So, How Many Post Types Are We Querying?

In this example, three different post types will be queried:

  • Posts
  • Podcasts
  • Courses

We Need Some Custom Fields

The first thing to do is create the required custom fields. As has been done in several previous posts, ACF (Advanced Custom Fields) is going to be used here. Both of these will be the “True/False” custom field type which creates a single checkbox. Again, there needs to be one for featured posts, and one for removing posts from the feed. When these are created, it will look something like this:

Custom Fields

Notice that these custom fields have only be assigned to the post types that we will be querying. Also, remember the names assigned to each of these custom fields. They will be needed later on in the code.

Let’s Get to Querying…

Now it’s time to put together a custom query that would loop through these post types. Well, actually two queries. The first query will loop through the post types and check for all of those that have the “Remove From Feed” custom field selected. It will then store the IDs of those posts in an array.

The second query will loop through the post types as well and check for those that have the “Featured” custom field selected and show those first. It will then fallback to the date for those without the custom field selected. It will also use the array from the first query to exclude the posts with the “Remove From Feed” selected.

Phew, that’s a lot of stuff going on, but stick with me as I break it down, it will get exciting I promise. A typical custom query with a single post type looks something like this:


$loop = new WP_Query(array(
   'post_type' => 'courses',
   'posts_per_page' => -1,
   'order' => 'DESC'
));

However, for the first query for this example looks like this:


$loop = new WP_Query(array(
   'post_type' => array('post', 'podcast', 'courses'),
   'fields' => 'ids',
   'post_status' => 'publish',
   'meta_query' => array(
      array(
         'key' => 'remove_from_feed',
         'value' => '1',
         'compare' => '==')
      )
   )
);

$removed = $loop->posts;

wp_reset_postdata();

Let’s break this down a little. For the post_type parameter, an array is used to include all three post types we need. The fields parameter is telling WordPress to return the IDs of these posts. The post_status parameter is searching for only those posts that have been published (rather than drafts or privately published).

Then, a meta_query is run and this is where the custom field gets checked. The key is the name of the “Remove From Field” custom field. Since this custom field is a “True/False” field, it’s value will be either 0 (unselected) or 1 (selected). Then the compare parameter states that the custom field needs to equal the value.

The $removed variable is an array of the post IDs that were grabbed from the query. wp_reset_postdata(); basically closes off the query so that another query can begin if necessary (and in this example…it is).

Onto the Next Query

Ok, you still with me? I know this is a lot, but we are getting there. Now, this second query is going to do the heavy lifting. Here’s what this query looks like:


$second_loop = new WP_Query(array(
   'post_type' => array('post', 'podcast', 'courses'),
   'post_status' => 'publish',
   'posts_per_page' => -1,
   'post__not_in' => $removed,
   'meta_query' => array(
      'relation' => 'OR',
      'featured_value' => array(
         'compare' => '=',
         'key' => 'featured',
         'value' => 1,
         'type' => 'NUMERIC'
      ),
      'featured_value_zero' => array(
         'compare' => '=',
         'key' => 'featured',
         'value' => 0,
         'type' => 'NUMERIC'
      ),
     'featured_value_ne' => array(
        'compare' => 'NOT EXISTS',
        'key' => 'featured'
     )
),
'orderby' => array('featured_value' => 'DESC', 'date' => 'DESC'),
);

So…yeah…that’s a long query. Let’s break down the key portions. Some of these parameters were already discussed in the last query, so no need to go over them again. The post__not_in parameter is using the $removed from the previous query. This excludes those posts from being part of this query, which makes the “Remove From Feed” custom field do it’s job.

There is also a meta_query here that does a number of things. First, it checks to see if the “Featured” custom field is checked. It then checks for those posts where the custom field is present, but is not checked and for those where the custom field doesn’t exist at all. The orderby parameter handles the sort order of these posts. It grabs the posts that do have the “Featured” custom checked and display those first. It then uses the date of publish to sort the rest.

Conclusion

So, there you have it. A query (or two) that grabs a handful of custom posts types and prioritizes those that are meant to be featured and removes those which shouldn’t be there. This is probably one of those fringe cases, but sometimes those are the things we need help with the most.