How To Set a Page as the Parent of a WordPress Custom Post Type

Custom Post Types in WordPress 3.x are designed to work from the root of your website i.e. a flat hierarchy.

In a recent project, we created a “Case Study” custom post type and added several new case study posts to our WordPress site.

Custom Post Type

We also created a page template called “Case Studies” that listed the new custom post types.

Everything was rumbling along just super until we noticed when viewing our new custom post types, the breadcrumb trail and URL permalink weren’t including the Case Studies page that we had clicked through from.

For example.

Our Case Studies page URL was /case-studies/ and the breadcrumb trail was also /case-studies/

URL ends in case-studies

When clicking on a case study custom post type “Land Owners”, both the URL and breadcrumb trail changed to “/land-owners/”.

URL changes to land-owners

Where had the Case Studies page gone in the hierarchy?

Let me be clear, this is not a WordPress bug but rather the way custom post types are currently implemented.  And yes, it is very annoying.

WordPress assumes that all custom post types are at the top of the hierarchy. i.e. they have no associated parent.

Unfortunately, this is exactly what we weren’t looking for.

The Solution

Of course, WordPress may change the way custom post types work in future versions but here’s a fix for now.

We’re going to create a meta box for the custom post type that allows us to enter the ID of a page or post and associate that with the post_parent variable, effectively making that ID our parent page.

We’re using the custom post type called “casestudy” here so change that value to your own custom post type name and copy the following into your functions.php file.

//Add the meta box callback function
function admin_init(){
add_meta_box("case_study_parent_id", "Case Study Parent ID", "set_case_study_parent_id", "casestudy", "normal", "low");
}
add_action("admin_init", "admin_init");

//Meta box for setting the parent ID
function set_case_study_parent_id() {
global $post;
$custom = get_post_custom($post->ID);
$parent_id = $custom['parent_id'][0];
?>
<p>Please specify the ID of the page or post to be a parent to this Case Study.</p>
<p>Leave blank for no heirarchy.  Case studies will appear from the server root with no assocaited parent page or post.</p>
<input type="text" id="parent_id" name="parent_id" value="<?php echo $post->post_parent; ?>" />
<?php
// create a custom nonce for submit verification later
echo '<input type="hidden" name="parent_id_noncename" value="' . wp_create_nonce(__FILE__) . '" />';
}

// Save the meta data
function save_case_study_parent_id($post_id) {
global $post;

// make sure data came from our meta box
if (!wp_verify_nonce($_POST['parent_id_noncename'],__FILE__)) return $post_id;
if(isset($_POST['parent_id']) && ($_POST['post_type'] == "casestudy")) {
$data = $_POST['parent_id'];
update_post_meta($post_id, 'parent_id', $data);
}
}
add_action("save_post", "save_case_study_parent_id");

There are three functions here.

  1. function admin_init() – This initialises the meta box for our casestudy custom post type, pointing to the call-back function that creates it
  2. function set_case_study_parent_id() – This is the call-back function that contains the HTML code needed to create the meta box on the casestudy custom post type when in edit mode
  3. function save_case_study_parent_id() – This saves the metadata from the new box into the DB

This will give you the following new meta box on your custom post type, allowing you to enter the ID of a page or post which it will set as the parent document.

Specifying the parent page ID

We set the meta box value to the ID of our “Case Studies” page which displayed all the custom post types.

Breadcrumb changes to Home > Case Studies > Land Owners

Great, now our Yoast breadcrumb trail was picking up the Case Studies parent page from our custom post type, but what about the URL?

There was just one thing left to do to bring everything together.

No slug had been explicitly defined for the custom post type, so they were being displayed in the URL from the server root with just the post title i.e. “/land-owners/”.

We needed to define a slug that would match up with our Case Studies page (slug “case-study”) so that our URL would read “/case-studies/land-owners/”.

Going back into the function that registered our custom post type, we added the following line to the $args array:

‘rewrite’ => array(‘slug’ => ‘case-studies’, ‘with_front’ => true)

This rewrites the URL slug from the server root to include “case-studies” in front of the post title.

Just for reference, here’s the complete custom post type registration function with the above line included.

/* Custom Post Types */
function register_custom_post_case_study() {

$labels = array(
'name' => _x('Case Studies', 'post type general name'),
'singular_name' => _x('Case Study', 'post type singular name'),
'add_new' => _x('Add New', 'Case Study'),
'add_new_item' => __('Add New Case Study'),
'edit_item' => __('Edit Case Study'),
'new_item' => __('New Case Study'),
'view_item' => __('View Case Study'),
'search_items' => __('Search Case Studies'),
'not_found' =>  __('No Case Studies found'),
'not_found_in_trash' => __('No Case Studies found in Trash'),
'parent_item_colon' => ''
);

$args = array(
'labels' => $labels,
'public' => true,
'publicly_queryable' => true,
'show_ui' => true,
'query_var' => true,
'rewrite' => array('slug' => 'case-studies', 'with_front' => true),
'capability_type' => 'post',
'hierarchical' => false,
'menu_position' => null,
'supports' => array('title','editor','author','excerpt','page-attributes')
);

register_post_type( 'casestudy' , $args );
}
add_action('init', 'register_custom_post_case_study');

Now our URL matches the breadcrumbs and the Case Studies pages are the parent of our custom post type.

As always, when working with Custom Post Types, navigate to Settings > Permalinks and click on save to regenerate the links for the new post types.

 

Looking to level up your WordPress development skills?

WordPress Plugin Development book

I self-taught myself WordPress development, but this book took it to another level.

Each chapter builds upon the next, but you can also use it to jump into specific areas like WP Cron, WP REST endpoints, building a custom plugin repository etc.

If you’re like me and like having reference books at hand, this is the one that sits on my desk all the time.

Was this article helpful?
YesNo

8 thoughts on “How To Set a Page as the Parent of a WordPress Custom Post Type”

  1. Hi Wil

    looks like I’ll be needing something similar to this. From when is this post? I see no date….

    my problem will be more generic. I got thousands of news items (technically posts) of an old system (different CMS) to migrate to WordPress posts. All pages have related posts, which are associated to a section/page.

    Each page’s related posts are queried from all the sub pages related posts, showing 2 most recent ones. Additionally, we can see ALL related posts from “here” and below.

    Of course, the url should append to current position

    /abc/def/article_1

    /abc/def/article_2 (even if belonging way down, not “here”)

    As your code above is hardcoding “Case Studies” (slug), this would not work.

    1. We need to give posts a page as parent (seems to work as of above)

    2. when clicked anywhere, it should appear as “member” of the current position (see example). If the parent page is stored in the post meta, I guess, the generated urls for related posts must be considering the post’s relation shiop and get the slug of the page to prepend the page slug.In order for that “wrong URL” to work, the link engine (rewrites) must accept that link, thus “know” that
    article_2 in
    /abc/def/article_2

    means “a posts ‘article_2’ ” (thus, searches for an article, after it has not found a page).

    This will be a challenge, if I maintain this requirement.
    But your post could be a valuable input to solve it. I’ll see. Not started yet.

    Thanks!

  2. any reason why i’d be getting /issues/england/ rather than /england/issues/? england is the page i want as the parent page and issues is the custom post type.

  3. Hi, i’ve done everything indicate in the article (except the ‘rewrite’ because it added 2 times what i wanted), it solve the url problem but now breadcrumb + post content display the 404 error message (it seems to use the index.php instead of single.php). Do you have any idea why it do this to me ?

    Best regards,
    Gautier

    1. OHHH i found why, in my register_post_type i deleted :

      ‘hierarchical’ => true,
      ‘has_archive’ => true,

      and it solved my problem ! 🙂

      1. Last thing i found : “One note that’s very, very important: your custom URLs won’t work until you go to Options → Permalink in wp-admin and re-save your current URL structure. This will flush WP’s current URL structure and add our new rewrite rules.”

  4. Nice ,,, but i want to think about different,, i have a site on Android Apps, i want to make a post as parent like : subway surfers Apk,,, which poses game description on the end of the post i want to show a link download now which will open a new page child where all links of download should be presenet how i can do that on my site http://www.appsandroidapk.com

Comments are closed.