Manually Curated Related Posts

There’s a bunch of plugins to automatically generate related posts. But some people prefer to have more control over their related posts and manually select them. This can be set up pretty easily using the Posts 2 Posts plugin.

There’s three pieces of code we’ll add to our core functionality plugin 1:

  1. Register the connection. This adds the metaboxes for linking posts.
  2. Pre-Loop code. This adds the related posts information to the $wp_query (better performance)
  3. Display Related Posts.

Register the Connection

<?php
/**
* Related Post Connection
* @author Bill Erickson
* @link http://www.billerickson.net/manually-curated-related-posts/
*
* @uses Posts2Posts
* @link https://github.com/scribu/wp-posts-to-posts/wiki
*/
function be_post_type_connections() {
p2p_register_connection_type( array(
'name' => 'related-articles', // unique name
'from' => 'post',
'to' => 'post',
'title' => array( 'to' => 'All Connections', 'from' => 'Related Articles' )
) );
}
add_action( 'p2p_init', 'be_post_type_connections' );
view raw gistfile1.php hosted with ❤ by GitHub

I’m creating this connection from the post post type, to the post post type. So when you’re editing a post, you can select other posts to connect it to. This actually creates two metaboxes: a from metabox that you use to connect the current post to others, and a to metabox that shows what posts have already been connected to this one. I’ve labeled the from metabox “Related Articles” since this is what is displayed as related to the current post, and I labeled to “All Connections”.

Once you add this code to your site, you should see metaboxes like this when editing a post:

Pre-Loop Code

<?php
/**
* Related Posts Before Loop
* Adds connection data to $wp_query. Run before the loop.
*
* @author Bill Erickson
* @link http://www.billerickson.net/manually-curated-related-posts/
*/
function be_related_posts_pre_loop() {
// Make Sure plugin is active
if ( !function_exists( 'p2p_register_connection_type' ) )
return;
global $wp_query;
p2p_type( 'related-articles' )->each_connected( $wp_query );
}
view raw gistfile1.aw hosted with ❤ by GitHub

As described in the Posts 2 Posts wiki, when dealing with archive pages its much better to add the connected posts to the $wp_query before you run the loop.

Display Related Posts

<?php
/**
* Display Related Posts
* @author Bill Erickson
* @link http://www.billerickson.net/manually-curated-related-posts/
*/
function be_related_posts() {
// Make Sure plugin is active
if ( !function_exists( 'p2p_register_connection_type' ) )
return;
global $post;
if( isset( $post->connected ) && !empty( $post->connected ) ):
echo '<div class="related-posts">';
$count = 1;
foreach( $post->connected as $related ):
if( $count < 6 ) {
echo '<div class="related-post">';
echo '<a class="image" href="' . get_permalink( $related->ID ) . '">';
$cat = wp_get_object_terms( $related->ID, 'category', array( 'count' => 1 ) );
echo '<span class="category">' . $cat[0]->name . '</span>';
echo get_the_post_thumbnail( $related->ID, 'be_home_small' );
echo '</a>';
echo '<a class="title" href="' . get_permalink( $related->ID ) . '">' . $related->post_title . '</a>';
echo '</div>';
$count++;
}
endforeach;
echo '</div>';
endif;
}
view raw gistfile1.aw hosted with ❤ by GitHub

You can of course display the related posts however you’d like. For my specific implementation, I wanted to limit it to 5, display the post image at a specific size, float the category name on top of the post image, and then display the title below the image.

The important thing to note is that $post->connected is an array of post objects, so use the foreach() to loop through them and then display the post data however you’d like.

Integrating it with your theme

Once you’ve added this code to your core functionality plugin, you have to actually call it in your theme. Just place be_related_posts_pre_loop() before you call the loop, and be_related_posts() somewhere inside the loop. If you’re using Genesis, it has hooks throughout the theme, so you just need to add the following two lines to your theme’s functions.php file (alter the second line to change the positioning of the related posts):

<?php
// Related Posts (see mu-plugins/lib/functions/related-posts.php
add_action( 'genesis_before_loop', 'be_related_posts_pre_loop' );
add_action( 'genesis_after_post_content', 'be_related_posts', 15 );
view raw gistfile1.aw hosted with ❤ by GitHub

I’d love to hear any thoughts you have about this, especially if you have experience with Posts 2 Posts.

  1. Core Functionality Plugin
    While you could put all this code in your theme’s functions.php file, a core functionality plugin is better for things like this because if you change your theme in the future, you’ll want to easily move this functionality to your new theme. If it’s in a plugin, all you have to do is add two lines to your theme (call the pre-loop code and then the display code). If all the code is in your old theme, you have to dig through all your files, figuring out how you built your related posts section so you can duplicate it in your new theme. In my core functionality plugin, I created /lib/functions/related-posts.php so that all the code was on one location.

Bill Erickson

Bill Erickson is the co-founder and lead developer at CultivateWP, a WordPress agency focusing on high performance sites for web publishers.

About Me
Ready to upgrade your website?

I build custom WordPress websites that look great and are easy to manage.

Let's Talk

Reader Interactions

Comments are closed. Continue the conversation with me on Twitter: @billerickson

Comments

  1. John O says

    Hi Bill,

    This is a great article and a great tutorial on how to get things up and running. Hope you don’t mind me asking a couple of questions:

    1. Is there a way to check if there are any related articles, so I know whether to output a section header, for example?
    2. How do I limit the number of related posts to display?
    3. What would happen if you tried to use this outside the loop on an archive page? Would it display any related posts related to any articles on that archive page?

    Thanks in advance!

    • Bill Erickson says

      1. Yes. Line 14 of this code snippet featured above checks to see if there’s connected posts. After it I add some markup before displaying the actual posts.

      2. On line 18 of the above-linked snippt, change $count < 6 to however many you'd like displayed. 3. No, it will not work outside the loop of an archive page.

  2. Salman Ahmad says

    Please make it easier for newbies to understand. First you mentioned plugins then the code, 🙁 Don’t know what to do 🙁
    Please share 2 lines with me so that I also come up with related posts.

    • Bill Erickson says

      I’m sorry but this tutorial is not designed for newbies. I recommend you use one of the plugins mentioned at the top for related posts rather than building the custom solution described in detail here.

  3. Hamish says

    How might you add a fall back to list other articles from the same category or tag?
    I would like to design my page so there are always 5 related posts, with the option to curate them. This was if an author is lazy and doesn’t make connections then there are still some listed similar posts?

    • Bill Erickson says

      Once you’re done looping through the connections, check the $count variable. If it’s less than what you want, do a WordPress query for posts in the same category, with ‘posts_per_page’ => 5 – $count

  4. Dale Bengston says

    I found this excellent post after 2-1/2 years – thanks Bill! Since this was written, Genesis has switched to support for HTML5 and added some new hooks. If you’re using a more-recent Genesis child theme that’s designed for HTML5, use the genesis_before_entry and genesis_entry_content hooks.

    If your child theme is designed to work with older Genesis versions and XHTML, stick with the genesis_before_loop and genesis_after_post_content hooks.

    Thanks again, Bill E. Terrific stuff!

  5. Neil Gowran says

    Used this today for the first time, works great! – and as the previous posted points out just use a more up to date hook such as genesis_entry_footer for the placement