How to display a random post in WordPress – alternative method

Whilst making some small improvements to our website recently, I came across a small problem displaying a random post in WordPress.

Background

Our website has a custom post type called “Testimonials”. We use to this to record our client testimonials which are then displayed in various places throughout our site. In addition to this, we wanted to display a random testimonial on the sidebar, so that each time a page loads a different testimonial is shown. Sounds simple, right?

First Steps – creating a shortcode

The first step was to write a new shortcode which I could drop into a standard text widget in our main sidebar. I called my shortcode [random_testimonial]. I won’t go into the details of creating a WordPress shortcode – this has been covered a lot elsewhere and the Shortcode API is a good resource if you need more information.

The next step was to implement the shortcode to retrieve and display our random testimonial. This shortcode would run when each time the sidebar is displayed. If you were looking to display other types of content in your sidebar or footer, you could easily modify this example to get display a random post or page, for example.

Attempt 1

This was the code I tried first. I’d already assumed this would be the way to do it, and I saw similar variants mentioned in other articles and forums:

if ( post_type_exists( 'testimonial' ) ) { 
 
   $testimonial_query = new WP_Query( array(
       'post_type' => 'testimonial',
       'orderby' => 'rand',
       'posts_per_page' => 1
   ) );
 
   if ( $testimonial_query->have_posts() ) {
      $testimonial_query->the_post();     
 
      // do something with post - e.g. the_excerpt(), the_content(), etc.
   }
   // Restore original post data
   wp_reset_postdata();
}

However, this didn’t work as expected. One first load, it seemed to select a testimonial at random, but on second, third and subsequent loads, the same testimonial was displayed each time.

After a couple of minutes head scratching, I realised the problem might be to do with the object and database caching on our server. Our site is hosted with WP Engine and the query above is exactly the sort of thing that would be cached by their highly efficient caching infrastructure.

So I needed to try another approach.

Attempt 2

global $post;

if ( post_type_exists( 'testimonial' ) ) { 
 
   $testimonial_query = new WP_Query( array(
       'post_type' => 'testimonial',
       'orderby' => 'rand',
       'posts_per_page' => -1
   ) );
 
   if ( $testimonial_query->have_posts() ) {
      $random_int = rand( 0, $testimonial_query->post_count - 1 );
      $post = $testimonial_query->posts[$random_int];     
      setup_postdata( $post );
      
      // do something with post - e.g. the_excerpt(), the_content(), etc.
   }
   // Restore original post data
   wp_reset_postdata();
}

And this time it worked as expected, loading a different testimonial on each page load.

The key here is returning all posts from the database ('posts_per_page' => -1) and then randomly selecting from the list of returned posts (i.e. testimonials). In this case, we only have 15 or so testimonials so it’s not a big database hit, and the query will be cached. But if you’re querying 1000’s of blog posts, you might want to limit your query to say 50 or 100, or perhaps limit on a certain category.

Once we have our list, we create a random number between 0 and the maximum length of the list (subtracting 1 as the array is zero-indexed). We then select the random testimonial from the posts list on the WP_Query object ($post = $testimonial_query->posts[$random]) and then call setup_postdata() on that post. Once we’ve done this, we can then call our usual loop functions such as the_title(), the_content(), etc., remembering to call wp_reset_postdata() afterwards to restore the original query’s post data.

Important note: Did you notice the global $post; line at the top of this code snippet? Well spotted. That’s because we have to pass the global $post object to setup_postdata() for this to work. So the variable we assign our random testimonial to must also be called $post!

Wrapping up

Hopefully this tip was useful to you. Let me know if you have any questions in the comments.