Divi WordPress Them — the smartest and most flexible WordPress theme

Back to Top

Show latest Posts from each Post Type

Previous Post:

Show latest Posts from each Post Type

On WordPress.org’s Support Forum, there are many questions asked regarding the same topic: How should we modify WordPress’s main query to have a fancier output, namely latest posts from each category or latest posts from each post type?

WordPress unfortunately does not provide any built-in function to handle these kinds of tasks, and it can be quite inefficient if we query for posts multiple times on one page, especially if you have a high enough number of posts per post type or per category. Luckily, getting latest posts from each post type is not as inefficient as from each category, and doable without much effort.

So, in this article I will try to describe all approaches that you can use to achieve such goal, and I hope they will be useful to you.

Get the Post Data

Using query_posts()

Normally, query_posts()1 gives you a mix of all fetched posts, for example, two posts from post type A followed by one post from post type B and then another post from post type A, ordered by post time.

In order to get latest posts from each post type, or in other words, grouped by post type, you will have to either use multiple query_posts() or filter the posts_groupby hook.

Using multiple query_posts()

Using multiple query_posts() is actually very simple:

  1. // query post type A
  2. query_posts('post_type=A&posts_per_page=1');
  3. // query post type B
  4. query_posts('post_type=B&posts_per_page=1');
// query post type A
query_posts('post_type=A&posts_per_page=1');
// query post type B
query_posts('post_type=B&posts_per_page=1');

If you have more than 10 post types, or you simply don’t want to rewrite any fraction of codes, you can do it like this:

  1. $public_post_types = get_post_types(array('public' => true)); // @see2
  2.  
  3. foreach ($public_post_types as $post_type)
  4. {
  5.     query_posts('post_type=' . $post_type . '&posts_per_page=1');
  6.     // output your posts here
  7. }
$public_post_types = get_post_types(array('public' => true)); // @see2

foreach ($public_post_types as $post_type)
{
	query_posts('post_type=' . $post_type . '&posts_per_page=1');
	// output your posts here
}

And if you would like to have more than one latest posts, simply change this part: posts_per_page=1 to any number you wish, e.g. posts_per_page=2, posts_per_page=3, etc.

As I have stated above, this approach can be inefficient if you have too many post types as well as posts per post type. In one of my tests with 10 post types and 1000 posts per post type, the result is as followed:

  1. // number of queries and time before query execution: 10 queries. 0.360 seconds
  2. query_posts('post_type=A&posts_per_page=1');
  3. query_posts('post_type=B&posts_per_page=1');
  4. query_posts('post_type=C&posts_per_page=1');
  5. query_posts('post_type=D&posts_per_page=1');
  6. query_posts('post_type=E&posts_per_page=1');
  7. query_posts('post_type=F&posts_per_page=1');
  8. query_posts('post_type=G&posts_per_page=1');
  9. query_posts('post_type=H&posts_per_page=1');
  10. query_posts('post_type=I&posts_per_page=1');
  11. query_posts('post_type=K&posts_per_page=1');
  12. // number of queries and time after query execution: 39 queries. 0.443 seconds
// number of queries and time before query execution: 10 queries. 0.360 seconds
query_posts('post_type=A&posts_per_page=1');
query_posts('post_type=B&posts_per_page=1');
query_posts('post_type=C&posts_per_page=1');
query_posts('post_type=D&posts_per_page=1');
query_posts('post_type=E&posts_per_page=1');
query_posts('post_type=F&posts_per_page=1');
query_posts('post_type=G&posts_per_page=1');
query_posts('post_type=H&posts_per_page=1');
query_posts('post_type=I&posts_per_page=1');
query_posts('post_type=K&posts_per_page=1');
// number of queries and time after query execution: 39 queries. 0.443 seconds

As you can see, apart from using about 29 queries (which is somewhat acceptable if you are not that into the number of queries), these queries take about 83 milliseconds to complete (tested with WordPress 3.0). Imagine you have like 5000 posts each, you will surely notice a slowdown in your blog’s performance.

Using the post_groupby hook

This hook can be found in wp-includes/query.php, and it allows you to interact directly with any SQL query generated by query_posts() or the WP_Query object. With this groupby hook, you can easily group your latest posts by post type using a simple filter like below (put it where you query for your posts):

  1. // Has a specific priority to remove the filter later - @see3
  2. add_filter('posts_groupby', 'bwp_posts_groupby', 11);
  3.  
  4. function bwp_posts_groupby()
  5. {
  6.     return 'post_type';
  7. }
  8.  
  9. // Use query_posts or create a new WP_Query object
  10. query_posts(array('post_type' => array('A', 'B', 'C' , 'D', 'E', 'F', 'G', 'H', 'I', 'K')));
  11.  
  12. // Remove the filter so it doesn't affect later queries
  13. remove_filter('posts_groupby', 'bwp_posts_groupby', 11);
// Has a specific priority to remove the filter later - @see3
add_filter('posts_groupby', 'bwp_posts_groupby', 11);

function bwp_posts_groupby()
{
	return 'post_type';
}

// Use query_posts or create a new WP_Query object
query_posts(array('post_type' => array('A', 'B', 'C' , 'D', 'E', 'F', 'G', 'H', 'I', 'K')));

// Remove the filter so it doesn't affect later queries
remove_filter('posts_groupby', 'bwp_posts_groupby', 11);

And the result for the same setup (1000 posts per post type and 10 post types)? Fewer database queries indeed, and also took faster to complete – approx. 51 milliseconds:

// number of queries and time before query execution: 13 queries. 0.360 seconds
/* Query for posts */
// number of queries and time after query execution: 16 queries. 0.411 seconds.

So, is this approach good enough? Probably, but the catch is that we can only have one latest post per post type. Furthermore, if your query_posts() is complex (i.e. uses a lot of arguments), filtering the groupby hook can produce unexpected outputs.

Using custom-made queries

In case you don’t know, query_posts() is designed to get all needed things from the database to help you conveniently setup and cache all post data. When you use template functions such as the_permalink()4, you are actually getting permalinks from the temporary cache.

When what you need is merely post titles, or post dates, query_post() will surely be an overkill and might be as well an inefficient approach (as demonstrated above). Using custom-made queries should be the way to go.

The custom query we are going to use looks like this:

  1. $latest_post_query = '
  2.     SELECT *
  3.         FROM
  4.         (
  5.             SELECT post_type, max( post_date ) AS mpd
  6.                 FROM ' . $wpdb->posts . "
  7.                 WHERE post_status = 'publish'" . '
  8.                 GROUP BY post_type
  9.         ) AS f
  10.         INNER JOIN ' . $wpdb->posts . ' AS s ON s.post_type = f.post_type
  11.         AND s.post_date = f.mpd
  12.     LIMIT 10';
  13.  
  14. $latest_posts = $wpdb->get_results($latest_post_query);
$latest_post_query = '
	SELECT *
		FROM
		(
			SELECT post_type, max( post_date ) AS mpd
				FROM ' . $wpdb->posts . "
				WHERE post_status = 'publish'" . '
				GROUP BY post_type
		) AS f
		INNER JOIN ' . $wpdb->posts . ' AS s ON s.post_type = f.post_type
		AND s.post_date = f.mpd
	LIMIT 10';

$latest_posts = $wpdb->get_results($latest_post_query);

It’s okay if you don’t understand a thing because I only want you to focus on the last line and take note of the get_results() function5 and the $latest_posts variable that contains our latest posts.

About efficiency, this uses just one query and takes about 22 milliseconds to complete for the same setup (10 post types and 1000 posts per post type). If you have 20 post types with 500 posts per post type, the result would be pretty much the same (which would take much longer if you use 20 query_posts()).

Now if you use this query as-is your posts should be sorted by their post types, but you may want to sort latest posts by post time instead. To do so simply change our custom-made query to this (the changed line has been highlighted):

  1. $latest_post_query = '
  2.     SELECT *
  3.         FROM
  4.         (
  5.             SELECT post_type, max( post_date ) AS mpd
  6.                 FROM ' . $wpdb->posts . "
  7.                 WHERE post_status = 'publish'" . '
  8.                 GROUP BY post_type
  9.         ) AS f
  10.         INNER JOIN ' . $wpdb->posts . ' AS s ON s.post_type = f.post_type
  11.         AND s.post_date = f.mpd
  12.     ORDER BY f.mpd DESC
  13.     LIMIT 10';
$latest_post_query = '
	SELECT *
		FROM
		(
			SELECT post_type, max( post_date ) AS mpd
				FROM ' . $wpdb->posts . "
				WHERE post_status = 'publish'" . '
				GROUP BY post_type
		) AS f
		INNER JOIN ' . $wpdb->posts . ' AS s ON s.post_type = f.post_type
		AND s.post_date = f.mpd
	ORDER BY f.mpd DESC
	LIMIT 10';

All the post fields should be available by now. For example if you want to display the title of each latest post from each post type, you can use this:

  1. foreach ($latest_posts as $latest)
  2. {
  3.     echo wptexturize($latest->post_title);
  4. }
foreach ($latest_posts as $latest)
{
	echo wptexturize($latest->post_title);
}

Of course it would be better if we are able to use those fancy template functions, i.e. the_title(), the_permalink(), get_post_meta(), you name it. This can be done easily by changing $latest to $post (our lovely global variable) but the hard part is how we can make use of the temporary cache mentioned earlier.

When we use a custom-made query like above WordPress will not cache a thing related to your post. Each time you use certain template functions, including the_permalink(), get_post_meta()6, the_category()7 or the_taxonomies()8, WordPress will use one additional query to build the output first before actually cache it, which is not a good thing (other functions like the_title(), the_content(), etc. can be used without worrying about the cache).

To benefit from caching for the_permalink() without the need for the additional query, we will have to add the cache ourselves using wp_cache_add()9, like so:

  1. $latest_posts = $wpdb->get_results($latest_post_query);
  2.  
  3. foreach ($latest_posts as $post)
  4. {
  5.     wp_cache_add($post->ID, $post, 'posts');
  6.     // no query added
  7.     the_permalink();
  8. }
$latest_posts = $wpdb->get_results($latest_post_query);

foreach ($latest_posts as $post)
{
	wp_cache_add($post->ID, $post, 'posts');
	// no query added
	the_permalink();
}

Note that the above method will only work if you do not have %category% in your permalink structure, because if you do, one additional query will be used to get the category associated with the post being processed, and there’s no easy way to get around such thing that I know of.

Similarly, you can generate cache for post meta using only one query for all posts fetched by our custom-made query:

  1. $latest_posts = $wpdb->get_results($latest_post_query);
  2.  
  3. // generate ids array
  4. foreach ($latest_posts as $latest)
  5. {
  6.     $ids[] = $latest->ID;
  7. }
  8.  
  9. // we try to get the meta cache for the first id
  10. $meta_cache = wp_cache_get($ids[0], 'post_meta');
  11. // if not found we create the cache for all other posts
  12. // 1 query needed
  13. if (!$meta_cache)
  14.     update_meta_cache('post', $ids);
  15.  
  16. // output your post meta
  17. foreach ($latest_posts as $post)
  18. {
  19.     echo get_post_meta($post->ID, 'thumbnail', true);
  20.     echo get_post_meta($post->ID, 'postviews', true);
  21. }
$latest_posts = $wpdb->get_results($latest_post_query);

// generate ids array
foreach ($latest_posts as $latest)
{
	$ids[] = $latest->ID;
}

// we try to get the meta cache for the first id
$meta_cache = wp_cache_get($ids[0], 'post_meta');
// if not found we create the cache for all other posts
// 1 query needed
if (!$meta_cache)
	update_meta_cache('post', $ids);

// output your post meta
foreach ($latest_posts as $post)
{
	echo get_post_meta($post->ID, 'thumbnail', true);
	echo get_post_meta($post->ID, 'postviews', true);
}

It is also possible to add the cache for categories or taxonomies manually, but unfortunately, the solution can be quite tricky and confusing, so I suggest that you let WordPress handle them. Don’t worry though, the number of queries used by this approach should never reach 29 like using multiple query_post(); and with the improved speed of up to 4 times faster, it is worth using, isn’t it ;)?

Actually, this approach would be perfect if it did not have some drawbacks. By now you should have noticed that it also shares the same weakness with the groupby hook: you can only have one latest post per post type.

While it is not impossible to get more than one, it is impossible to get more than one efficiently (without using advanced SQL statements that make your custom query a mess, ugh). Anyway, you might try using this instead of the above query to get more than one posts:

  1. $latest_post_query = "(SELECT * FROM $wpdb->posts WHERE post_type = 'A' ORDER BY post_date LIMIT 2)
  2. UNION ALL
  3. (SELECT * FROM $wpdb->posts WHERE post_type = 'B' ORDER BY post_date LIMIT 2)
  4. UNION ALL
  5. (SELECT * FROM $wpdb->posts WHERE post_type = 'C' ORDER BY post_date LIMIT 2)
  6. UNION ALL
  7. (SELECT * FROM $wpdb->posts WHERE post_type = 'D' ORDER BY post_date LIMIT 2)
  8. ... repeat this until you are satisfied ... ";
  9.  
  10. $latest_posts = $wpdb->get_results($latest_post_query);
$latest_post_query = "(SELECT * FROM $wpdb->posts WHERE post_type = 'A' ORDER BY post_date LIMIT 2)
UNION ALL
(SELECT * FROM $wpdb->posts WHERE post_type = 'B' ORDER BY post_date LIMIT 2)
UNION ALL
(SELECT * FROM $wpdb->posts WHERE post_type = 'C' ORDER BY post_date LIMIT 2)
UNION ALL
(SELECT * FROM $wpdb->posts WHERE post_type = 'D' ORDER BY post_date LIMIT 2)
... repeat this until you are satisfied ... ";

$latest_posts = $wpdb->get_results($latest_post_query);

Don’t yell at me, it is simply an idea, it does work but is not common. If you need more than one latest posts, stick with query_posts(), it will scale better.

Another drawback is the custom-made query uses no term relationship, which means it will get posts from all terms (for example you want posts from ‘movie’ post type but only want posts from ‘horror’ term to show up). The topic of this article is post-type-based after all, not category/term-based. In a follow-up to this post: Show latest Posts from each Category we will look into this issue again. Check it out if you are interested, otherwise you can again stick with query_posts(). If you think you can’t live with query_posts() either, use a cache plugin, that’s all I can recommend now =P.

Show the Post Data

Now that you’ve got data fetched from database, showing them is an easy task regardless of what approach you choose. Basically, all template tags are available for you to use, the only difference is, with the custom query approach, you will have to tell WordPress to use our data (remember to add some caching methods above), like so:

  1. foreach ($latest_posts as $latest_post)
  2. {
  3.     $post = $latest_post;
  4.     // HTML markups for your post
  5. }
  6.  
  7. /* or */
  8.  
  9. foreach ($latest_posts as $post)
  10. {
  11.     // HTML markups for your post
  12. }
foreach ($latest_posts as $latest_post)
{
	$post = $latest_post;
	// HTML markups for your post
}

/* or */

foreach ($latest_posts as $post)
{
	// HTML markups for your post
}

Keep in mind that we are dealing with different post types so each post type can have its own taxonomies (e.g. post type ‘movie’ has a taxonomy named ‘genre’ and so on). You will need to, therefore, use appropriate template tags, e.g. the_category() will only work with category rather than other taxonomies. For taxonomies in general, you will use the_taxonomies() instead (you might also want to take a look at get_the_term_list()10.)

The Bottom Line

Choosing an appropriate approach greatly depends on what you need. If you have about half a dozen post types and an average number of posts per post type, you can safely use the first approach, which is most complete and supported by the core WordPress. When your blog seems to perform a little slower than it normally would, you can try other approaches, which can be as easy and efficient to use.

The question that needs answering is,

References

  1. http://codex.wordpress.org/Function_Reference/query_pos ... uery_posts []
  2. http://codex.wordpress.org/Function_Reference/get_post_ ... post_types [] []
  3. 16-remove_filter-and-remove_action-bug/ [] []
  4. http://codex.wordpress.org/Function_Reference/the_perma ... _permalink []
  5. http://codex.wordpress.org/Function_Reference/wpdb_Clas ... wpdb_Class []
  6. http://codex.wordpress.org/Function_Reference/get_post_ ... _post_meta []
  7. http://codex.wordpress.org/Function_Reference/the_categ ... e_category []
  8. http://codex.wordpress.org/Function_Reference/the_taxon ... taxonomies []
  9. http://codex.wordpress.org/Function_Reference/WP_Cache# ... _functions []
  10. http://codex.wordpress.org/Function_Reference/get_the_t ... _term_list []
Print Article Trackback Trackback to this Article   Subscribe to Comments RSS Subscribe to Comments RSS

11 Opinions for Show latest Posts from each Post Type (1 Trackback)

  1. User's Gravatar
    1
    Werner February 8, 2011 at 4:15 pm – Permalink

    If I have 5 post types only, is using query_posts ok?

    • User's Gravatar
      2
      OddOneOut February 9, 2011 at 7:43 am – Permalink

      Thanks for your question!

      As I have stated in the article, it actually depends on how many posts you have per post type but generally speaking you will be fine with 5 post types :)

  2. User's Gravatar
    3
    Ruddy February 13, 2011 at 10:16 pm – Permalink

    I have about 120 posts for each post type and this does reduce the loading time, thank you!

  3. User's Gravatar
    4
    PrinceCharming April 11, 2011 at 9:13 pm – Permalink

    Worked beautifully, thank you!

  4. User's Gravatar
    5
    iShow May 14, 2011 at 10:45 pm – Permalink

    Hi could you please tell me which custom query you use for this website’s homepage? Is it one latest post per category or something?

    • User's Gravatar
      6
      OddOneOut May 16, 2011 at 3:39 pm – Permalink

      Yes it is, but it is much more complicated than one latest post per post type, I’m planning to write a follow-up to this post, but I’m not sure when…

  5. User's Gravatar
    8
    yoav February 21, 2012 at 11:09 pm – Permalink

    Great Article. Solved the issue perfect. here is a modified sql did for filter out spesific custom posts

    1. SELECT *
    2.         FROM
    3.         (
    4.             SELECT post_type, max( post_date ) AS mpd
    5.                 FROM wp_posts WHERE post_status = 'publish' AND post_type NOT
    6. IN ('cpt1',  'page',  'cpt2',  'nav_menu_item')
    7.         GROUP BY post_type
    8.         ) AS f
    9.         INNER JOIN wp_posts AS s ON s.post_type = f.post_type
    10.         AND s.post_date = f.mpd
    11.     LIMIT 10
    SELECT *
    		FROM
    		(
    			SELECT post_type, max( post_date ) AS mpd
    				FROM wp_posts WHERE post_status = 'publish' AND post_type NOT 
    IN ('cpt1',  'page',  'cpt2',  'nav_menu_item') 
    		GROUP BY post_type
    		) AS f
    		INNER JOIN wp_posts AS s ON s.post_type = f.post_type
    		AND s.post_date = f.mpd
    	LIMIT 10
    • User's Gravatar
      9
      Chris June 1, 2012 at 3:09 am – Permalink

      yoav,

      Nice work! =)

      How would one be able to set a date range instead of limiting it to most recent?

  6. User's Gravatar
    10
    Musik+ June 5, 2012 at 1:15 am – Permalink

    Worked beautifully, thank you :)

  7. User's Gravatar
    11
    Pablo November 21, 2012 at 4:50 am – Permalink

    Thanks a lot!! incredible post!!

  1. Speed Up WordPress: A Developer's POV - Better WordPress

    [...] using query_posts() is also highly discouraged. The reason is described clearly in this article, which shows us noticeable difference in generation time between query_posts() and our own [...]

Speak Up Your Mind!

An asterisk (*) indicates a required field and must be filled.




  • Web page and e-mail addresses turn into links automatically.
  • Wrap codes in: <code lang=""></code> or <pre lang="" extra="">
  • Lines and paragraphs break automatically.

Next Post: