I am trying to make a custom loop that will display the latest 5 posts from select categories, all using a single query. I want the loop to display the category name followed by the 5 posts, and then the next category, etc.

I followed this answer and it seems to do what I want, except it doesn't output the categories properly.

For example, let's say I want to display categories 100,101,102 and 103, it should display like this:

100 Post 1 Post 2 Post 3 Post 4 Post 5

101 Post 1 Post 2 Post 3 Post 4 Post 5


The Problem

However, currently, if there is a post in category 100, and also in category 250, it will also display the title of category 250 too, like so

250 Post 1

How do I get it that it ONLY displays the categories specified?

Here's the code I have so far, based on this.


$args = array(
    'posts_per_page' => -1,
    'tax_query' => array(
            // arguments for your query
            'taxonomy' => 'category',
            'field' =>  'id',
            'terms' => $selected_categories, // $selected_categories contains an array of post IDs.

$query = new WP_Query($args);   
$q = array();

while ( $query->have_posts() ) { 


    $a = '<a href="'. get_permalink() .'">' . get_the_title() .'</a>';

    $categories = get_the_category();

    foreach ( $categories as $key=>$category ) {

        $b = '<a href="' . get_category_link( $category ) . '">' . $category->name . '</a>';    


    $q[$b][] = $a; // Create an array with the category names and post titles

/* Restore original Post Data */

foreach ($q as $key=>$values) {
    echo $key;

    echo '<ul>';
        foreach ($values as $value){
            echo '<li>' . $value . '</li>';
    echo '</ul>';


Thanks for any help!

2 Answers 2


I have used Custom Select Query to gather all information in a single call and hope it may work out.

global $wpdb;
$selected_cat_id = '6,7,8,9';
$post_count = 5;

$custom_query = "SELECT $wpdb->posts.ID, $wpdb->posts.post_title, $wpdb->terms.name FROM $wpdb->posts  
LEFT JOIN $wpdb->term_relationships ON ($wpdb->posts.ID = $wpdb->term_relationships.object_id) 
LEFT JOIN $wpdb->terms ON ($wpdb->term_relationships.term_taxonomy_id = $wpdb->terms.term_id) 
WHERE 1=1 AND ($wpdb->term_relationships.term_taxonomy_id IN ($selected_cat_id))
AND $wpdb->posts.post_type = 'post' AND ($wpdb->posts.post_status = 'publish' OR $wpdb->posts.post_status = 'private') ORDER BY $wpdb->posts.post_date ASC";
$pageposts = $wpdb->get_results($custom_query, OBJECT);

$cat_posts = array();
foreach($pageposts as $post){
    $cat_posts[$post->name][$post->ID] = $post->post_title;

foreach($cat_posts as $key => $values) {
    $category_id = get_cat_ID( $key );
    echo '<a href="'.get_category_link($category_id).'">'.$key.'</a>';
    $values = array_slice($values,0,$post_count,true);
    echo '<ul>';
        foreach ($values as $post_id => $value){
            echo '<li><a href="'.get_permalink($post_id).'">' . $value . '</a></li>';
    echo '</ul>';
  • Thanks @Karthick, but I want to do this using only ONE wp_query. Your code is going to run a new query for every category.
    – Edegist
    Commented May 4, 2020 at 22:57
  • @Edegist Answer has been modified to get result in a single query. You can give a try.
    – Karthick
    Commented May 5, 2020 at 8:36
  • Thanks a lot! that worked great! I never really used $wpdb before, so I don't really know whats going on, but it works. Also, If I want to include a template for each post instead of using <ul> and <li>, I'm assuming I need to use setup_postdata( $post ); but that didn't seem to work, as the template loads with blank data. e.g the post titles etc aren't loading.
    – Edegist
    Commented May 6, 2020 at 21:48
  • Also, using Query Monitor, it seems like this code generates around 30 queries to the db if I call 3 categories (with 5 posts each).
    – Edegist
    Commented May 6, 2020 at 22:38
  • @Edegist it is returning 30 columns of data because the MySQL query is having LEFT JOINS with category table. Still you can filter the query and get only whatever column needed. I have updated the answer slightly to get only 3 columns - $wpdb->posts.ID, $wpdb->posts.post_title, $wpdb->terms.name
    – Karthick
    Commented May 7, 2020 at 6:23

Use 'category_name' as direct argument in your $args array, not inside your tax_query array. I always displayed posts of a category on my pages in that way.


Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.