Relating Posts, Pages, and Custom Post Types in WordPress

I received a question from a fellow WordPress designer/developer recently on how I was able to bring content from related pages and posts into any page, post, or custom post type.

“I like the way you pull in posts featured images and also page featured images on the bottom of pages.  It keeps the site hoping.  I wasn’t able to find a plugin that would pull in featured images on both posts and pages and all of them seemed to be recent posts or random.  And then I tried to use categories in post and pages to break it up and that didn’t seem to work.  I finally conceded to just pulling in the most recent two posts.”

I think this may be a pretty frequent task for designers and developers, so I’ve posted my response to it here.

In the end, all we’re trying to accomplish is get a list of content thumbnail images (and maybe title and description) from a pool of content. This has been accomplished loads of times already with plugins that generate lists of post or page thumbnails. The big difference here, is that the method I describe lets you categorize any post type and then limit the pool of related content to whatever category is associated with the current post item.

Relating Posts, Pages, and Custom Post Types

Essentially, the question here is “How do you bring in related content of your choosing to the current page?” There are a ton of ways to answer this question. Some do it with a plugin like the solid Yet Another Related Posts Plugin using an algorithm to automatically relate posts. Many do it with a random list of posts, or the most recent posts.

These are good ways to go, but they lack in providing the hand-picked relatability and extendability of actually selecting which items should relate to others. To get that kind of functionality, we have to look to either the database, custom meta-fields, or hackish arrays stored in options tables.

DB Relating Many to ManyI came at it from the database level–as opposed to hard-coding an array of related items or writing code to create a custom meta field that would handle it. From the database angle, the question becomes, “What does WordPress have in place that can help me create a many-to-many relationship (one to many posts related to one-to-many other posts). In the database world, many-to-many relationships are typically handled with a join table that sits between them.

Using Custom Taxonomies to Associate Content

Since WordPress 2.8, it has become easier to relate anything in the wp_posts table (posts, pages, attachments, custom post types, etc) with each other through the use of custom taxonomies and custom post types. Here is the approach I take, using a furniture store for an example.

  1. Create your site content
    This can be a posts, pages, or–what I often do–a custom post types like “furniture.”
  2. Create a custom taxonomy that will act to relate to items together.
    I personally like the plugins “Types – Complete Solution for Custom Fields and Types” or “GD Custom Posts And Taxonomies Tools” to quickly and easily create both custom taxonomies and custom post types.I usually name the taxonomy something that makes sense like rooms, furniture types, collections, etc.The taxonomy can be hierarchical (like categories) or not (like tags).  Be sure to set your taxonomy to be accessible by (show up on) the post type(s) you need to relate to each other.

    In the case of this furniture site example, I need my custom post type of “furniture” to relate to my custom post type of “collections”, but this could just as easily be “posts” and “pages.”

    Note here that you don’t have to use a custom taxonomy; instead, you could just use the built-in categories and tags by either keeping all your work within posts or by adding tags and categories to your pages. See code sample 1 below.

  3. Add a couple of terms to the taxonomy.
    For example, for my taxonomy of “rooms” I’d add terms like dining room, living room, bedroom, and office.
  4. Categorize (or tag) each content piece with its appropriate term.
    Then, make sure that the post/page/custom post type you want to relate it to also has been categorized (or tagged) with the same term. You can select as many terms as you’d like for each.

Pools of Content

At this point you’ve created pools of related content by giving them a common term. Now you just have to access those. Accessing them means doing a little coding in either your functions.php file or your template file. There may be plugins out there doing this exact thing but I’m not aware of them. So, I wrote my own shortcode that does the trick (see code sample 2).

  1. On the post/page/custom post you want to show the list of common content, use the shortcode, defining the taxonomy you want to use to relate the posts. So, for our furniture example, I would use [common_content tax=room] on a page I called Dining Room Furniture and categorized as “dining room” in the custom taxonomy of “room”. This would create a list on that page of posts/pages that are also categorized with dining room.
  2. I can further specify the number to show, the post types to include, to pick randomly from the matching pools, etc.

This makes it possible to show specific, hand-picked content from other posts as a list in your current post, as opposed to random, recent, or related by algorithm (like YARPP does).

For Example:

In the case of the furniture store example, I created a custom taxonomy called “Furniture Types” that is available to both the “Furniture” and “Collections” post types. Then I used terms for furniture types that were pretty broad, like “barstools,” “beds,” “chairs,” “tables,” etc.

I also related collections and furniture through a second custom taxonomy called “Collection Matchings.” The terms I used for the Collection Matchings taxonomy are specific to vendors and their collection names, like: “Family Grove Heidelberg,” “Oakcraft Mission Bedroom,” “Oakcraft Mission Dining,” “Woodwind Mission” etc.

I then used the excellent “Taxonomy Images” plugin to give each term a picture that shows in my listing pages.

Code Samples:

1. Add categories and tags to WordPress pages

add_action('admin_init','my_admin_init');
function my_admin_init(){
register_taxonomy_for_object_type('post_tag', 'page');
register_taxonomy_for_object_type('category', 'page');
}

2. Shortcode to grab posts sharing the same term(s)

This could easily be turned into a plugin, but I just use it personally so I haven’t taken that extra step because it would mean documenting the code and attributes :-).

Paste this code into your functions.php file and then use the shortcode wherever you’d like, in posts, pages, sidebar widgets, or theme templates.

'category',
		'posts_per_page'=>4,
		'orderby'=>'name',
		'order'=>'ASC',
		'hide_empty'=>false,
		'description'=>true,
		'show_link'=>true,
		'show_title'=>true,
		'show_thumbnail'=>true,
		'thumbnail_size'=>'thumbnail',
		'show_description'=>true,
		'post_type'=>array('page','post'),
		'list_id'=>'content-list'
		), $atts ) );

	$out=null;
	$terms = array();

	//uses the current post/page/custom post type that's in the loop
	global $post;

	//convert post_type to an array for our query use below if it's not the default
	//this allows us to accept the shortcode argument as a comma-separated list
	if(!is_array($post_type)){
		$post_type = explode(",",$post_type);
	}

	//get the terms of the current post for the requested taxonomy
	$termlist = get_the_terms($post->ID,$tax);
	if($termlist && !is_wp_error($termlist)){
		foreach($termlist as $term){
			$terms[]=$term->slug;
		}
	}

	//get the list of matching posts/pages/custom post type
	$args = array(
		'post_type'=>$post_type,
		'posts_per_page'=>$posts_per_page,
		'tax_query'=>array(
			array(
				'taxonomy'=>$tax,
				'field'=>'slug',
				'terms'=>$terms
				)
			)
	);

	$content = get_posts($args);

	if($content && !is_wp_error($content)){

		$out.="
    \n"; foreach($content as $item){ $out.="
  • "; $linkstart = null; $linkend = null; if($show_link===true || $show_link==="true"){ $linkstart = ''; $linkend = ''; } if($show_thumbnail===true || $show_thumbnail==="true"){ if(has_post_thumbnail($item->ID)){ $out.= $linkstart.get_the_post_thumbnail($item->ID,$thumbnail_size).$linkend; }else{ $out.="
    No Image
    "; } } if($show_title===true || $show_title==="true" || $show_description===true || $show_description==="true"){ $out.='
    ' if($show_title===true || $show_title==="true"){ $out.='

    '.$linkstart.$item->post_title.$linkend.'

    '; } if($show_description===true || $show_description==="true"){ $out.="
    ".apply_filters('the_content',$item->post_content)."
    \n"; } $out.="
    \n"; } $out.="
  • \n"; } $out.="
\n"; } return $out; } ?>