I created a custom post type "Book", and successfully added to WordPress' admin menu as a root category. I can successfully list the post type contents with /wp-admin/edit.php?post_type=test_book & can add via /wp-admin/post-new.php?post_type=test_book.

However, I would like to add a custom post type "Chapter" (test_chapter) to "Book", by allowing users to choose chapters (and re-arrange the order in post-new.php), thus establishing the parent-child relationship.

How can I do so?

2 Answers 2


The code you need to use here has some steps. First you need to create e metabox on your custom post type edit.php which will list all your posts of test_chapter post type. Then you have to hook the save_post action so you set the parent of your edited post to the chosen "test_chapter". The best way is to connect these two post types as post meta and not as parent-child relation so you can handle it better. According to this tutorial: http://wptheming.com/2010/08/custom-metabox-for-post-type/

add_action( 'add_meta_boxes', 'add_books_metaboxes' );
    // Add the Book Meta Boxes

    function add_books_metaboxes() {
        add_meta_box('wpt_test_chapter', 'Book chapter', 'wpt_test_chapter', 'test_book', 'side', 'default');

// Book chapter Metabox

function wpt_test_chapter() {
    global $post;

    $args = array(
    'posts_per_page'   => -1,
    'post_type'        => 'test_chapter',
    $books_array = get_posts( $args );
    $chapter_ids = get_post_meta($post->ID, 'related_chapters', true);
    <select name="book_chapters" multiple>
    foreach($books_array as $book_array){
        if(in_array($book_array->ID, $chapter_ids)){$selected = 'selected;'}
        else{$selected = '';}
        echo '<option '.$book_array->ID.' '.$selected.'>'.$book_array->post_title.'</option>';
    // Noncename needed to verify where the data originated
    /* if you are using plugin:
    echo '<input type="hidden" name="eventmeta_noncename" id="eventmeta_noncename" value="' . 
    wp_create_nonce( plugin_basename(__FILE__) ) . '" />';

// Save the Metabox Data

function wpt_save_chapter_meta($post_id, $post) {

    // verify this came from the our screen and with proper authorization,
    // because save_post can be triggered at other times
    /* if you are using plugin:
    if ( !wp_verify_nonce( $_POST['eventmeta_noncename'], plugin_basename(__FILE__) )) {
    return $post->ID;
    // Is the user allowed to edit the post or page?
    if ( !current_user_can( 'edit_post', $post->ID ))
        return $post->ID;

    // OK, we're authenticated: we need to find and save the data
    // We'll put it into an array to make it easier to loop though.

    $chapters_ids['book_chapters'] = $_POST['book_chapters'];

    // Add values of $events_meta as custom fields

    foreach ($chapter_ids as $chapter_id) { // Cycle through the $events_meta array!
        //if( $post->post_type == 'revision' ) return; // Don't store custom data twice
        update_post_meta($post->ID, 'related_chapters', $chapter_id);


add_action('save_post', 'wpt_save_chapter_meta', 1, 2); // save the custom fields

Have not tested the code so it may have some mistakes. Let me knwo so I can correct them.


First you create test_book function and call add_action() function to initialize this function.


add_action('admin_menu', 'test_book');

Then after you define test_book() function functionality, for adding menu and sub-menu you declare your function test_book() like this manner....

function test_book(){
    add_menu_page( "Books", "Books", "manage_options", "test_book", "test_book", "" );
    add_submenu_page( "test_book", "Chapters", "Chapters", "manage_options", "test_chapter", "test_chapter");
  • But this does not connect the relationship between Book & Chapter CPT.
    – Raptor
    Commented Jan 29, 2016 at 6:04
  • Books is primary/parent menu and Chapters is submenu of books Commented Jan 29, 2016 at 6:36
  • I'm not talking about menu hierarchy. As mentioned in question, I would like to select chapters when I'm adding / editing book.
    – Raptor
    Commented Jan 29, 2016 at 6:41
  • global $wpdb; $data = $wpdb->get_results("select * from wp_chapter where 1=1"); $optionchapter = "<select name='auditChapter' id='auditChapter' onchange='getBook(this.value)'>"; if(count($data)>0){ $optionchapter = "<option value=''>--Select Chapter--</option>"; foreach($data as $key => $val){ $optionchapter .= "<option value='".trim($data[$key]->chapter_id)."'>".trim(strtoupper($data[$key]->chapter_name))."</option>"; } } else{ $optionchapter .= "<option value=''>--Select Chapter--</option>"; } $optionchapter = "</select>"; echo $optionchapter; Commented Jan 29, 2016 at 7:12
  • Where should I put this piece of code? Can you edit your answer and put the codes inside? Hard to read codes in comment. Thanks.
    – Raptor
    Commented Jan 29, 2016 at 7:18

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.