Create dynamic sub posts for a WordPress custom post type

Published 2012-05-10 · Archived article

I needed a way to have sub posts (pages) automatically created for my custom post types. Tabbing the content with javascript would have been insufficient and that is not the correct way to deal with a large amount of content. Oddly, there are no plugins that I am aware of equivalent to the Drupal Views and Panels modules. It took me about a while to find a virtual or “fake pages” solution that fit my needs and allowed me to dynamically create pages with custom fields.

My sub-pages content is created from custom custom fields displayed neatly on post edit screens within meta boxes. This tutorial will assume you have an understanding of creating and managing custom fields – or that you have a plugin like the Meta Box plugin installed.

Here is the structure of my project site, using the custom post type “books”:

  • Book: To Kill a Mockingbird (mysite.com/books/to-kill-mockingbird/ – the actual post)
    • Author Biography (mysite.com/books/to-kill-mockingbird/author/) – virtual child page
    • Purchase Book (mysite.com/books/to-kill-mockingbird/purchase/) – virtual child page
    • Reviews (mysite.com/books/to-kill-mockingbird/reviews/) – virtual child page

The method I am using is heavily based upon a post @ BetterWP.net with some exceptions: We are using a custom post type, and the content for each page is not created by using or tags. Every post in your configured custom post type will have the child pages. This is 100% correct for my implementation, and will be exactly what most of you require.

Update: I added a demo and a child theme for Twenty-Eleven. View them here.

Update 2: This method will ONLY work if you have “/%postname%/” for permalinks.

Functions.php or plugin file

This code can go into your functions.php but due to the number of lines your best bet is placing this code in /wp-content/plugins/fake-pages/fakes.php.

At each highlighted line, you will need to change “books” to your custom post type name. “Reviews“, “Purchase“, and “Author” should be changed to match your required page names. You can have as many child pages as you like, however, the URL will work for EVERY item created in your custom post type. This tutorial is based off of the “Twenty Eleven/Twenty Ten themes”.

<?php
/*
Plugin Name: Mysite.com Fake Pages
Plugin URI: http://www.mysite.com
Description: Creates fake sub pages for a custom content type.
Version: 1.0
Author: my name
Author URI: http://mysite.com
/* ----------------------------------------------*/

    // Fake pages' permalinks and titles. Change these to your required sub pages.
    $my_fake_pages = array(
        'reviews' => 'Reviews',
        'purchase' => 'Purchase',
        'author' => 'Author Bio'
    );
     
    add_filter('rewrite_rules_array', 'fsp_insertrules');
    add_filter('query_vars', 'fsp_insertqv');
     
    // Adding fake pages' rewrite rules
    function fsp_insertrules($rules)
    {
        global $my_fake_pages;
     
		$newrules = array();
		foreach ($my_fake_pages as $slug => $title)
			$newrules['books/([^/]+)/' . $slug . '/?$'] = 'index.php?books=$matches[1]&fpage=' . $slug;
     
        return $newrules + $rules;
    }
     
    // Tell WordPress to accept our custom query variable
    function fsp_insertqv($vars)
    {
        array_push($vars, 'fpage');
        return $vars;
    }

    // Remove WordPress's default canonical handling function
	
    remove_filter('wp_head', 'rel_canonical');
    add_filter('wp_head', 'fsp_rel_canonical');
    function fsp_rel_canonical()
    {
        global $current_fp, $wp_the_query;
     
        if (!is_singular())
            return;
     
        if (!$id = $wp_the_query->get_queried_object_id())
            return;
     
        $link = trailingslashit(get_permalink($id));
     
        // Make sure fake pages' permalinks are canonical
        if (!empty($current_fp))
            $link .= user_trailingslashit($current_fp);
     
        echo '<link rel="canonical" href="'.$link.'" />';
    }

?>

DO NOT forget to flush your permalinks! Go to Settings > Permalinks > Save to flush


Post type requirements

Your post type must have the following capabilities:

'public' => true,
'publicly_queryable' => true,
'query_var' => true,
'rewrite' => true,
'hierarchical' => true,

More information on registering custom post types: http://codex.wordpress.org/Function_Reference/register_post_type


Yoast WP-SEO Canonical Fix

If your using Yoasts WordPress SEO plugin, you need to remove the canonical link from your custom post type as we set proper canonical tags above. Below I disable canonical tags for “books” posts. Place this code in your functions.php if using the Yoast SEO plugin.

function wpseo_canonical_exclude( $canonical ) {
		global $post;
		if (is_singular('books')) {
    		$canonical = false;
    }
	return $canonical;
}

add_filter( 'wpseo_canonical', 'wpseo_canonical_exclude' );

Creating template files for parent and sub pages

Now, if you look at the tutorial I’m basing this off of (my previous post), they use “nextpage” to add content. That would be a MESS. Instead, all of my sub-pages are using custom custom fields. If you need to add large blocks of text, simply add an additional WYSIWYG text area to the edit post page.

Required Template files:

You will need to create templates like the ones below:

  • Main: single-books.php
    • Parent Post: single-books-index
    • Author Page: single-books-author
    • Purchase: single-books-author.php
    • Reviews: single-books-reviews.php

single-books.php

Copy your single.php template and rename it to single-[post-type-name].php. Mine is single-books.php

Find:

<?php get_template_part( 'content', 'single' ); ?>

Replace With:

		<?php if (!$current_fp) {
				get_template_part( 'single', 'books-index' );
			} else if ($current_fp == 'reviews') {
				get_template_part( 'single', 'books-reviews' );
			} else if ($current_fp == 'author') {
				get_template_part( 'single', 'books-author' );
			} else if ($current_fp == 'purchase') {
				get_template_part( 'single', 'books-purchase' );
			}; ?>
                

Find:

get_header();

After, add:

$current_fp = get_query_var('fpage');

Creating content template files

Save a copy of content-single.php as single-[post-type-name]-index.php. Since this is the post page, I am using the_content() to show the main content entered on the post edit screen. Mine is single-books-index.php. *note* I already wrote the child theme using “single”, it would be more appropriate to prefix with “content” like the original.

Save copies of single-[post-type-name]-index.php that correspond to your sub-page names:

  1. single-books-reviews.php
  2. single-books-author.php
  3. single-books-purchase.php

Your going to want to modify these pages to fit your needs. I assume you know how to include your custom fields into a page template. Download the example files at the end of the tutorial if you need more guidance.


Page navigation

You could do a loop to setup your navigation. I just hard coded my navigation like this:

<ul id="books-nav">
<li class="menu-item"><a href="<?php echo get_permalink(); ?>">Overview</a></li>
<li class="menu-item"><a href="<?php echo get_permalink() ?>reviews/">Book Reviews</a></li>
<li class="menu-item"><a href="<?php echo get_permalink() ?>author/">Author Bio</a></li>
<li class="menu-item"><a href="<?php echo get_permalink() ?>purchase/">Where to Purchase</a></li>
</ul>

You can check the current menu item using something similar to this:

if ($current_fp == 'reviews') { echo ' current-menu-item'; }

Title Tags

Since we cannot use WordPress SEO or All-in-One SEO pack to set our page titles, we need to set the page titles with a filter. This method will work fine with plugins that modify page titles.

Open your Functions.php or Plugin file, and add this to the end:

// Modify page titles
   add_filter('wp_title', 'fsp_titletag', 11, 3);
	
	function fsp_titletag($orig_title)
    {
			global $wp_the_query;
		    $current_fp = get_query_var('fpage');
			
			if (!$current_fp) {
				// If this is not a new page, use the existing title
				$title = $orig_title;
			} 
			else if ($current_fp == 'reviews') { 
			      $title = the_title() .' - Reader Reviews and Ratings - Yoursite.com';
			}		
			else if ($current_fp == 'purchase') { 
			      $title = 'Buy' . the_title() .' - Find the best price online';
			}
			else if ($current_fp == 'author') { 
			      $title = 'About the Author -'. the_title();
			};		
			return $title;  
    }

The titles won’t be very flexible like this, but work just fine for my implementation. You could look into using a custom field here to set a page title for each page.


H1 Page Titles

Again, my titles are hard coded within each template file. I use:

<h1 class="post-title"><?php the_title(); ?> Reviews</h1>

Download Twenty-Eleven Child Theme

I created a Twenty-Eleven child theme with a working example based upon books. Use these files as an example if you had trouble with any of the above, or need help with adding custom fields and bringing it all together 🙂

Download the child theme: Twenty Eleven Child Theme

View a live example: Demo Server – Down for maintenance..

Discussion

Previously published comments, retained for reference. New comments are closed.

Jonathon

Chris, Thank you for posting this, was very handy, and I was hoping you might have some insight on adapting this a bit for a custom condition? Just as your example, I have a Custom Post Type with lots of custom fields in each post. I have the content split across 3 templates now using the fake pages method above. The catch is, I have a second Custom Post Type that needs to load into the above mix, and needs to integrate into the URL scheme somehow. The primary CPT is Sports and displays posts such as Football, Baseball, Soccer, etc. and the info for those posts is segmented into 3 fake pages. I need a 4th fake page in that mix though, still based on any current given sport, that displays team photos from a Team Photos CPT (with registered taxonomies of gender, sport, and year). So, I currently have sitename.com/sport/football/byyear which would show the initial page and query for the team photos, but navigating through the team photos, the links want to swap to sitename.com/teamphotos/nameofpost, when ideally, it would be sitename.com/sport/football/byyear/teamphotos/mens/1999 (or similar). Any thoughts on how to accomplish this with the rewrites? Thank again, Jonathon

Chris Rutherford

I sent you an email - your comment had been marked as spam and I hadn't noticed it.

Tudor

Hello, First of all many thanks for the writeup, it might be a lifesaver for a new project I'm starting now. I've managed to successfully replicate the functionality, but I'm wondering if there's any way I could remove the rewrite base for the custom post type and still make this work. So in you case it would mean having mysite.com/to-kill-mockingbird/author/ instead of mysite.com/books/to-kill-mockingbird/author/. I've managed to remove the base with a plugin, but this also breaks the fake pages. Any thoughts would be greatly appreciated. Thanks, Tudor

Tudor

And btw, Happy New Year! :)

Chris Rutherford

I have a site on my development server using this that I'm working on now. I'll take a quick look and see if it's possible. I know we are removing the category base from posts but leaving them for the custom post type due to our structure - shouldn't be too hard to apply to the CPT.

Chris Rutherford

Take a look at the code under "Functions.php or plugin file". On line 28, change: [php]$newrules['books/([^/]+)/' . $slug . '/?$'] = 'index.php?books=$matches[1]&fpage=' . $slug;[/php] to: [php]$newrules['([^/]+)/' . $slug . '/?$'] = 'index.php?books=$matches[1]&fpage=' . $slug;[/php] Then, flush your rewrite rules (just go to Settings > Permalinks and click "save"). That should do the trick.

Tudor

Works! :)

Johnny Goebel

Hi, Great tutorial. I've almost got everything working. It works all the way to the single-post-index.php. However when I try to link the fake sub page it comes back with Page Not Found. I'm using the Atahualpa theme, and WP 3.4. I believe I've followed everything perfectly. I can send you whatever files you'd need to look at. Thanks, Johnny

Chris Rutherford

Send all related files to [email protected]

Johnny Goebel

Sent. Thanks!

Johnny Goebel

My man is legit. Took a look at my code. Problem was I didn't have the plug in activated (duh on me). Works great!

Chris

This is a great solution! I'm trying to add my custom taxonomies to the rewrite rules and hit a roadblock. Could you lend a hand? I want it to work as follows: trips/%taxonomy1%/%taxnomy2%/%postname%/ There might be an instance where only one of the taxonomies is being used or there will be nesting. Any help would be amazing.

Steve

Hello, thanks for providing this code. It works great for a single Custom Post Type. How would I get it to work for multiple Custom Post Types? I am working on a Project that has 17 Custom Post Types. I am trying to add a fake page for a details link. So each Page from each Custom post Type needs to have a Fake Details Page. Any help you could provide would be greatly appreciated. Thanks, Steve

Waleed Asender

Hi, I would like to thank you alot for this hack it saved my life. And just FYI it worked for me even though im using “/%category%/%postname%/” permalinks method. Thanks again,

Pablo

Hello Chris. This post is amazing and almost perfect to my use because i need one more level like: mysite.com/books/to-kill-mockingbird/author-list/the-author. How can I do that?

Pablo

I have another question. How can I use pagination? I'm trying plugin or manually, but no way works.

Ray

I need to find an answer to exactly the same question - how to include taxonomy terms in the slug - @Chris Rutherford, do you know how this might be possible? Great code - thanks! Q

Ray

The answer is to do something along the lines of: $newrules['books/(.*)/(.*)/([^/]+)/' . $slug . '/?$'] = 'index.php?book=$matches[3]&fpage=' . $slug; The important part is the "$matches[3]" - the index should make the placement of the search term - in our case, we have two taxonomy terms and then the CPT post slug - so we take the 3rd match. Q

Aulia Ardianto

Hi Steve, I'm facing a similar problem with multiple post types, did you managed to make it work? Thanks.

Andy

Great tutotrial, but I believe there is a little mistake. In single-book.php you used $current_fp but it only worked for me after using $fpage instead.

Peter

Thanks a lot! Can't get this to work with "Polylang"-Plugin - which creates a url structure wit an addtional /en/ in front..... is there a solution fpor this?

paul

Thank you very much for this. I'd been messing around trying to get my own custom rewrite rules to work for ages without success.

Luyen

Hi Chris, I've been wrestling with the same challenge for the last few weeks before I kick off a project. I'll be using custom fields (ACF) to store a lot of data that is really meant to be viewed as "Panels" or "Partials" as you would with Drupal. The approach I was thinking about was to use an Ajax function to load each sub-section, to keep the # of queries down i'd try to reference IDs in the custom fields for images, other posts etc, and avoid embedding images directly into the WYSIWIG. The biggest downside is of course from an SEO and URL stand-point, none of these pages would actually exist. It'd be essentially a JS toggle between sub-sections. I'm thinking I could use a combination of your approach with "actual" fake pages, and ajax functionality so I don't end up loading too much data on the initial page load. What do you think? :-)

Mark

Thanks for the great tutorial. Can an adjustment be made to handle one more level? Something like: Post: mysite.com/books/to-kill-mockingbird/ Subpage #1: mysite.com/books/to-kill-mockingbird/author-list/ Subpage #2: mysite.com/books/to-kill-mockingbird/author-list/author Thanks for your help.

Mark

Hi Pablo. I know it's been a couple years, but I wanted to see if you ever solved the "one more level" custom post type issue? I'm having the same issue, and thought I'd touch base to see if you had a solution. Thanks

Sonika

Big-big-big thanks, Chris! Works excellent!!! WP 4.8

Zemma

Hi! I can't change the page title to anyting other than Hello World. I deleted the Hello World post and now 'Page not found' is the title. Can you help me out?

Amit

Hi I’m also looking for same. My website have multiple post types. Kindly guide, How would I get it to work for multiple Custom Post Types Thanks.

Prashant Rajput

Thanks, It works perfect. But i need pagination on the page so how can we add this? eg. 10.7.0.105/company/aurora/articles This i have created by referring your article. Now I want pagination on the article page so how to do that. 10.7.0.105/company/aurora-cannabis/articles/1 10.7.0.105/company/aurora-cannabis/articles/2

Create sub page of sub pages using “fake pages” &#8211; PHP

[…] found this tutorial to make so called fake pages, so I could dynamically create pages with custom fields. It works very […]

Sohail Ahmed

Not working with wordpress 5.7.2. Sub pages /author/, /reviews/ .. loads single content only and not single-books-author.

Max

Hi. Great solution, but child pages not in sitemap.xml, so it's no indexable in search. Maybe there is a solution to fix this? Thanks