I have two custom post types: "request" and "quote". Both have the "hierarchical" flag set to true.

A 'request' cpt can be the parent of one or more 'quotes' cpt.

Since these are two different post types, WordPress admin has seperate overview lists (e.g. edit.php?post_type=request) for both.

What I am looking for is a way to format the request overview the same way a hierarchical page list is formatted, for example:

  • Request 1
    • Quote 1
    • Quote 2
  • Request 2
    • Quote 3
    • Quote 4

I only need to do this on the admin side (as I'm not using the front-end).

  • I think if you want to show hierarchy you should do it the same way pages work, by selecting a page parent. By default, wordpress does not provide a way to select page parent for custom post types, but people have found a way around it.janina.tumblr.com/post/3588081423/post-parent-different-type.
    – gdaniel
    Commented Mar 10, 2014 at 14:15
  • That is a usefull resource which I already discovered and implemented before posting this question, but thanks ;-)
    – Marc
    Commented Mar 12, 2014 at 7:46

1 Answer 1


The way WordPress works for hierarchical post types is that post parent should always be the same post type of children.

For that reason, in admin screens, the post type in edit.php is pretty hardcoded.

However, what WordPress do is to run a WP_Query where 'post_type' argument is the current one.

So, what you can do?

First of all, if you want to merge the 2 cpt admin screens, you can hide the admin screen for 'quote'. It is very simple: when you register the post type, simply set 'public' argument to false:

$args = array(
   // all other args here
  'hierarchical' => TRUE
  'public' => FALSE
register_post_type( 'quote', $args );

Doing so, the quote admin screen is not shown, so you have only the 'request' one.

What you have to do is intercept the query and set 'post_type' argument to array containg both 'request' and 'quote'.

The problem doing so is that the global 'post_type' variable will be set to that array, but WordPress expects 'post_type' a string ending up in some errors. To avoid that errors, we should find a hook to force the global 'post_type' to be the string 'request'.

After a quick look at the file responsible to output the admin posts table: 'wp-admin/includes/class-wp-posts-list-table.php' I found that a good hook for the scope can be the filter hook 'edit_posts_per_page'.

So the code:

add_action( 'pre_get_posts', function( $query ) {
  if ( is_admin() ) { // do nothing if not is admin
    $scr = get_current_screen();
    // if in the right admin screen
    if ( $scr->base === 'edit' && $scr->post_type === 'request' ) {
      // force query to get both 'request' and 'quote' cpt
      $query->set( 'post_type', array( 'request', 'quote' ) );
      // force global $post_type to be = 'request' if is an array 
      add_filter( 'edit_posts_per_page', function( $perpage ) {
        global $post_type;
        if ( is_array( $post_type ) && in_array( 'request', $post_type ) ) {
          $post_type = 'request';
        return $perpage; // we don't want to affect $perpage value 
      } );
  return $query;
} );
  • Your code basically just works so much obliged. I will accept this answer and move on to the next logical step: figuring out how to make the parent/child relationshop visible on the edit screen, because right now the different post types are ordered by name or date.
    – Marc
    Commented Mar 12, 2014 at 7:49

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.