WordPress: Custom post types and parent-child relationships




Creating a parent-child relationship in WordPress is not difficult, but when the parent and the children are of a different content type, creating the relationship can be a bit tricky.

About custom post types

A custom post type is actually a way we have to classify our content within WordPress. WordPress has several content types from the get-go: posts, pages, attachments, and so on 1.

A post or a page has several attributes, such as title, categories, content (the editor where you write the post itself) and so on. A custom post type is simply a content type that is different from a post or a page and that can have other attributes. An example: A custom post type called PROJECT. A project can have a title, a description, a time span, a location, a manager, and so on.

WordPress treats all content types the same way (as posts) and stores each object/post of a content type in the same database table, under the post type attribute. If you have access to your WordPress database, you can see that posts are for instance stored with post type “post” while pages are stored with the post type “page”.

How to make your own custom post type

If one does not have a great deal of experience in creating custom post types or WordPress itself, I would recommend to use an online generator. That, to ensure all needed code is present and for simplicity purposes. It’s also possible to write all the PHP code from scratch if one so desires, but normally, on a project situation, we have got no time for that, so using a generator would save some time.

The architecture

Let’s say that our website structure is like this: We have a location. A location can have zero, one or more projects under itself. A project can have zero, one or more articles under itself. Basically, it would look something like this:

WordPress doesn’t really have a means of creating this kind of architecture/structure, so we are going to do something else in order to achieve it.

First, we are going to make two custom post types: project, which is going to work as the parent, and project article, which is going to work as the child. As mentioned, WordPress treats all content types equally in the database, so in reality, project and project article would actually be siblings, but we don’t need to care about that right now.

The Project custom post type

Below is the code for our first custom post type: PROJECT (the parent). The code is pretty standard, but there are a couple of things we need to keep in mind. Refer to the bold parts within the code block below:

// Register Custom Post Type
function custom_post_type_project() {

  $labels = array(
  'name'  => 'Projects',
  'singular_name' => 'Project',
  'menu_name' => 'Projects',
  'name_admin_bar' => 'Projects'
  );
  $args = array(
    'label' => 'Projects',
    'description' => 'This is a project content type. ',
    'labels' => $labels,
    'supports' => array( ),
    'taxonomies' => array( 'location' ),
    'hierarchical' => true,
    'public' => true,
    'show_ui' => true,
    'show_in_menu' => true,
    'menu_position' => 5,
    'show_in_admin_bar' => true,
    'show_in_nav_menus' => true,
    'can_export' => true,
    'has_archive' => true,		
    'exclude_from_search' => false,
    'publicly_queryable' => true,
    'capability_type' => 'page',
    'query_var => 'projects',
    'rewrite' => array( 'slug' => 'projects'),
    );
  register_post_type( 'project', $args );

}
add_action( 'init', 'custom_post_type_project', 0 );

The Project Article custom post type

Below is the code for the project article custom post type:

// Register Custom Post Type
function custom_post_type_project_article() {

$labels = array(
	'name' => 'Project articles',
	'singular_name' => 'Project article',
	'menu_name' => 'Project articles',
	'name_admin_bar         => 'Project articles'
);
$args = array(
	'label' => 'Project article',
	'description' => 'A project article',
	'labels' => $labels,
	'supports' => array( ),
	'taxonomies' => array( 'category' ),
	'hierarchical' => false,
	'public' => true,
	'show_ui' => true,
	'show_in_menu' => true,
	'menu_position' => 5,
	'show_in_admin_bar' => true,
	'show_in_nav_menus' => true,
	'can_export' => true,
	'has_archive' => true,		
	'exclude_from_search' => false,
	'publicly_queryable' => true,
	'capability_type' => 'page',
);
register_post_type( 'project_article', $args );

}
add_action( 'init', 'custom_post_type_project_article', 0 );

Now we have both our custom post types. What we need to do is to connect them in such a way that the CPT project works as the parent of project articles.

Parent-child relationship

To establish a parent-child relationship between two custom post types, I’ve followed the instructions on this article.

This is the whole code for the parent-child relationship, but I’ll explain it further down the post.

// FIRST PART
add_action('admin_menu', function() { 
   remove_meta_box('pageparentdiv', 'project_article', 'normal');
});

// SECOND PART
add_action('add_meta_boxes', function() { 
   add_meta_box('project_article-parent', 'Projects', 'project_article_attributes_meta_box', 'project_article', 'side', 'high');
});
  
// THIRD PART
function project_article_attributes_meta_box($post) {	
    $post_type_object = get_post_type_object($post->post_type);
 
   	if ( $post_type_object->hierarchical ) {
      		$pages = wp_dropdown_pages(array(
            		'post_type' 		=> 'part', 
            		'selected' 		=> $post->post_parent, 
			'name' 			=> 'parent_id', 
            		'show_option_none' 	=> __('(no parent)'), 
            		'sort_column'		=> 'menu_order, post_title', 
			'echo' 			=> 0
		));
      
      		if ( ! empty($pages) ) {
         		echo $pages;
      		} 
    	}
}

Let’s split the code and see what it does:

// FIRST PART
add_action('admin_menu', function() { 
   remove_meta_box('pageparentdiv', 'project_article', 'normal');
});

This code block removes the page parent attributes from the project articles dashboard, so to speak. That is, the metabox 2 where you can specify a project article’s parent page. Since we want our project articles to have a parent of content type project and not project article, we have to remove this box.

// SECOND PART
add_action('add_meta_boxes', function() { 
   add_meta_box('project_article-parent', 'Projects', 'project_article_attributes_meta_box', 'project_article', 'side', 'high');
});

The second code block above adds a new metabox to the project article’s dashboard.

  • This metabox is called project_article-parent
  • The metabox’s title is Projects
  • It is added to the project article’s dashboard
  • It is placed in the right column (side) and has high priority (high), so it’ll be the topmost meta box
  • The third parameter project_article_attributes_meta_box is the function we are calling, we’ll see what this function does in a minute

The following is the function we are calling when the metabox is added to the dashboard.

// THIRD PART

function project_article_attributes_meta_box($post) {	
    $post_type_object = get_post_type_object($post->post_type);
 
   	if ( $post_type_object->hierarchical ) {
      		$pages = wp_dropdown_pages(array(
            		'post_type' 		=> 'part', 
            		'selected' 		=> $post->post_parent, 
			'name' 			=> 'parent_id', 
            		'show_option_none' 	=> __('(no parent)'), 
            		'sort_column'		=> 'menu_order, post_title', 
			'echo' 			=> 0
		));
      
      		if ( ! empty($pages) ) {
         		echo $pages;
      		} 
    	}
}

What this function esentially does is to fill a dropdown inside the metabox we added in the second step above with custom post objects of type Project. The code works as follows:

  1. First we create a variable where we store the post type object
  2. We check if the post type object is hierarchical. Remember we specified that the custom post type Project was hierarchical above? Since this is true, then the code proceeds to the next step
  3. We create a new variable called $pages. We assign to it a WordPress function called wp_dropdown_pages 3. This function displays a list of pages without a submit button. Again, this function takes a bunch of parameters/arguments in array form. (You can read more about this function by following the link provided)
  4. We perform a new check, that if the variable pages is not empty, that is, that pages of the custom type Project do exist, show them

To summarize this part, what all the steps above do is to display a little box on the Project article edit post dashboard, with a dropdown menu filled with the titles of all the existing posts of the Project type. In order to create the parent-child relationship, when you are writing a project article, you will select a Project post from the dropdown menu, and thus that Project post will be assigned as the parent of the article.


Permalinks

Permalinks can be a little tricky with CPT and parent-child relationships. In my project case, I wanted to display all the children (all project articles) of all projects on the index.php-page. I also wanted to add a link for each project article’s parent project. In order to do so, I had to code permalinks for the parents and the children like this:

The parent permalink:

$parent_permalink = get_permalink($post->post_parent);

The child permalink (project article, the posts)

the_permalink();

There is a couple of things to take care of when having parent-child relationships like this and permalinks. I will update this article later on with more info.