With the introduction the custom post type feature, WordPress has made a very important move from just a blogging solution (very powerful but limited with it’s concept) to full-fledged CMS system. All 3.x releases more or less are evolving steps in the same direction. But some quite common tasks that we’re expecting from CMS are still missing in the Core leaving the battlefield open for plugin developers. One such task is creating and management of many-to-many relationships between posts of different types.

The Practical Guide to Multiple Relationships Between Posts in WordPress

Fortunately there is an excellent plugin “Posts 2 Posts” by Silviu-Cristian Burcă aka scribu which solves that task. It’s beautifully coded and deadly simple to use. It provides as with very simple but powerful API allowing the creation of connections and the out-of-the-box administrative UI allows you to manage them. With the following tutorial I’m going to show you how to use this plugin to create and manage connections between posts of different types in practical steps.

Let me first describe the task. Assume we have a custom post type at our WordPress-powered site named “project” to represent (how surprisingly) projects of some nature (eg. some downloadable apps or plugins). At the same time we post notes or updates related to these projects in the blog (representing with default “post” post type). Of course we would like to clearly state this relation – in every project’s page we’d like to have links to notes in the blog and vice versa – in every blog post we’d like to have links to related project(s). This could be achieved manually but the disadvantages of such an approach are quite obvious. So we would use Post 2 Post plugin to make this possible through the following steps:

  • register “project” post type and register it’s connection with “post” post type
  • create connections between published items using appropriate metabox
  • create template tags to help us list related posts or projects
  • edit “single” post template to use our introduced template tag for presenting related items

Bonus: At the end we’ll make things a bit more complicated to see the full power of Posts 2 Posts plugin – we’ll provide registered relationships with additional metadata nearly the same way we could have such metadata for posts themselves.

A Note about Code Placement

Following Thord Daniel Hedengren’s (wpmu.org) advice I’d try to be smart enough and would not recommend you to include the following code into the theme functions.php file. Instead I would recommend to create your own Custom Functionality Plugin to handle all code that is connected with content of your site and powers the functionality that should not disappear just because you switched the theme. So it’s the perfect place for registering custom post types and creating relationships between them. It’s worth mentioning also that to make the code below work you need to install Post 2 Post plugin first and activate it in WordPress admin.


  function frl_p2p_registrations(){            /* Register 'project' post_type */            $register_args = array(          'label' => __('Projects', 'frl'),          'labels' => array(              'name' => __('Projects', 'frl'),              'singular_name' => __('Project', 'frl')          ),          'public' => true,                  'rewrite' => array('slug'=>'project', 'with_front'=>false),          'show_in_menu' => true,               'hierarchical' => false,          'menu_position' => 5,          'supports' => array('title','editor','thumbnail', 'excerpt'),              'show_in_nav_menus' => false      );            register_post_type('project', $register_args);          /* Register connection */            $connection_args = array(          'name' => 'project_post',          'from' => 'project',          'to'   => 'post',          'sortable'   => 'any',          'reciprocal' => false,          'admin_box' => array(              'show' => 'any',              'context' => 'normal',              'can_create_post' => true          ),          'admin_column' => 'any'      );            p2p_register_connection_type($connection_args);    }    add_action('init', 'frl_p2p_registrations');  

The heart of Post 2 Post API is a p2p_register_connection_type function which is quite similar to other register_something WP functions and allows us to register relations between posts of different types and customise it’s behaviour through the set of accepted parameters:

  • name – an unique identifier of connection type
  • from – post type(s) on “from” side of the connection
  • to – post type(s) on “to” side of the connection
  • prevent_duplicates – whether to allow duplicated connections
  • sortable – whether to allow connections to be ordered via drag-and-drop.
  • reciprocal – whether to allow the same post type on “from” and “to” sides of connection
  • admin_column – whether to display column with related posts list in posts table in admin
  • admin_box – set of parameters that customise connection’s metabox appearance, in particular:
    • show – whether to show default metabox and where (“any”, “from”, “to”)
    • context – regular metabox context parameter (“normal” or “advanced”),
    • can_create_post – whether to allow create new post directly from connection metabox

For the full set of registration parameters and their default values please consult the core/api.php file of the plugin.

First we prepare the registration of project post type as described in the Codex. Then we register connection with post post type with necessary parameters. All these registrations we wrap into a separate function to be able to call it upon init hook.

Creating Connection in Metabox

Creating Connection in Metabox

P2P plugin creates a connection metabox on edit post screens for connected post types if it was configured upon registration. The usage of this metabox is quite intuitive. Simply start typing the desired post name in the search box, select the appropriate item from the drop-down list and click the add icon. To remove a connection click trash icon. Create as many connections per each post/project as needed.

Among other admin UI goodies P2P plugin creates (if configured) the column for a list of connected items in the posts’ management table for each of the connected post types. The latest version of the plugin introduces also an informational admin screen (located under “Tools” menu) containing reference data about registered connections and their parameters.

Listing Related Posts

  function frl_list_related($post_id, $title = ''){    $query_args = array(      'connected_type' => 'project_post',      'connected_items' => intval($post_id),              'nopaging' => true  );    $query = new WP_Query($query_args);    if($query->have_posts()):    if(empty($title))      $title = __('Related items', 'frl');  ?>      <h3><?php echo $title; ?></h3>      <ul class="related-items">    <?php while($query->have_posts()): $query->the_post(); ?>  <li><a href="<?php the_permalink();?>"><?php the_title();?></a></li>  <?php endwhile;?>    </ul>  <?php  endif;  wp_reset_postdata();  }  

One of the greatest advantage of P2P plugin is an ability to get connected posts using standard (and familiar) WP_Query syntax. Everything you need is to pass additional query variables to limit set of posts:

  • connected_type – the identifier(s) of the connection used when registering
  • connected_items – ID of post(s) from one side of the connection to search
  • connected_direction – optional parameter allowing to restrict the direction of queried connections (“from”, “to”, “any”), when omitted the direction is inferred from connected_type + connected_items combination.

In our example we wrap the query into a separate function – frl_list_related template tag. This function query connects posts based on submitted $post_id parameter. If the query produces any results the function lists them using the standard WordPress Loop approach. It also accepts $title parameter to customise the title of related items’ block depending of context.

Template Editing

  /* code for project template - single-project.php */    if(function_exists('frl_list_related'))      frl_list_related($post->ID, __('Related Posts', 'frl'));    /* code for post template - single.php */    if(function_exists('frl_list_related'))      frl_list_related($post->ID, __('Related Project(s)', 'frl'));  

Finally we should edit the template files that represent “project” and “post” post types respectively (most likely the single-project.php and single.php files of your theme) so they included the call of our new template tag. We also passed the title string when calling our function for setting the correct title of the connected posts list. The result (eg. for particular “project”) after some styling is similar to the following:

Related Items

More Complicated Example

Assume that blog posts related to a particular project could be divided into different types, eg. changelog and how-to information. On the project page we’d like to list posts of different types separately and apart from that accompany each link with a short comment. All these enhancements could be easily achieved using the connection metadata functionality provided by P2P plugin. Connection metadata behaves the same way as other types of metadata supported by WordPress (eg. postmeta, usermeta etc.). To use them we will implement the following steps:

  • rewrite connection registration function to support “type” and “comment” meta-fields
  • add metadata values to existing (and new) connections
  • rewrite post listing function to behave as described above
  • use new function in template
  function frl_p2p_registrations(){            /* Register 'project' post_type */            $register_args = array(          'label' => __('Projects', 'frl'),          'labels' => array(              'name' => __('Projects', 'frl'),              'singular_name' => __('Project', 'frl')          ),          'public' => true,                  'rewrite' => array('slug'=>'project', 'with_front'=>false),          'show_in_menu' => true,               'hierarchical' => false,          'menu_position' => 5,          'supports' => array('title','editor','thumbnail', 'excerpt'),              'show_in_nav_menus' => false      );            register_post_type('project', $register_args);          /* Register connection */            $connection_args = array(          'name' => 'project_post',          'from' => 'project',          'to'   => 'post',          'sortable'   => 'any',          'reciprocal' => false,          'admin_box' => array(              'show' => 'any',              'context' => 'normal',              'can_create_post' => true,              'fields' => array(                                  'type' => array(                      'title' => __('Type', 'frl'),                                          'values' => array(                          'changelog' => __('Changelog', 'frl'),                          'tutorials' => __('Tutorials', 'frl')                      )                  ),                  'comment' => array(                      'title' => __('Comment', 'frl')                  )              )          ),          'admin_column' => 'any'      );            p2p_register_connection_type($connection_args);    }    add_action('init', 'frl_p2p_registrations');  

We have added fields array to the list of connections registration parameters. The used configuration creates two fields in the connection metabox: the “type” field allows you to select from two possible options and the “comment” field allows you to type comment text.


After filing in the metadata for all created connections we can access it the using “meta_query” syntax or using P2P API function p2p_get_meta. Our new listing template tag is going to use both.

  function frl_list_related_by_type($post_id, $type, $title=''){      global $post;            $query_args = array(          'connected_type' => 'project_post',          'connected_items' => intval($post_id),                  'nopaging' => true,          'connected_meta' => array(              array(                  'key' => 'type',                  'value' => $type,              )          )      );            $query = new WP_Query($query_args);            if($query->have_posts()):            if(empty($title))          $title = __('Related items', 'frl');  ?>          <h3><?php echo $title; ?></h3>          <ul class="related-items">        <?php while($query->have_posts()): $query->the_post(); ?>      <li>          <a href="<?php the_permalink();?>"><?php the_title();?></a>          <span class="comment-meta"><?php echo p2p_get_meta($post->p2p_id, 'comment', true );?></span>      </li>  <?php endwhile;?>        </ul>  <?php      endif;      wp_reset_postdata();  }  

First of all, our renewed function accepts the $type parameter which allows us to select related posts based on stored “type”-field value. We achieve this using connected_meta query variable. It has syntax similar to regular WordPress meta_query query. Then when printing each found post inside the loop we access the value of its “comment” meta-field with p2p_get_meta function. Each related post found with our query stores the id of the appropriate connection in the $post->p2p_id property. With this id p2p_get_meta allows us to get any connection metadata by it’s key. The third boolean parameter indicates whether we are expecting a single value or array of possible values (as it required by WordPress Metadata API).

  /* code to list 'changelog'-type posts on project template - single-project.php */    if(function_exists('frl_list_related_by_type'))  	frl_list_related_by_type($post->ID, 'changelog', __('Project's Changelog', 'frl'));  

The result of this function implemented somewhere in the single-project.php file for “changelog”-type posts (as an example) could look as follows:

Related Items 2

And so this is practically the end of our demonstration. You can download the whole PHP code as a separate plugin below.


There are a number of discussions in the WordPress community whether such features as posts relationships should be in the core or not. The practical result of these discussions belongs to the future. Meanwhile we could solve this relationship problem, in particular, with the help of Posts 2 Posts plugin through a few easy steps.

Of course, the example used in the tutorial is quite a basic one but it demonstrates the logic and flexibility of the P2P approach and its excellence in terms of integration with the core. If you have examples of interesting and creative implementations of this approach please share your experience with us. Lastly, let me use the last sentence to say many thanks to the P2P developer for the great and inspiring work.

To show main source of content: