Custom post types are amazing. If you know how to use them, it means the difference between building simply “a WordPress site” (something that anyone can do) and building “a website powered by WordPress” (which is what we do).

Custom post types have a couple of quirks that need to be worked around, though. Here’s one: if you want to set up a custom post type to behave like a Page, that’s easy—just set the hierarchical parameter to true when registering it. But you’ll find that it won’t be compatible with all of the page-based functions right out of the box.

For instance, say you want to do an interior page menu. Here’s what we use for our sidebar.php:

1
2
3
4
5
6
7
8
9
10
11
12
13
/**
 * PAGE SIDEBAR
 * Loads all of the children of the top-level page in the current section.
 */
global $post;
if ($post->post_parent) {
    $ancestors = get_post_ancestors($post->ID);
    $root = count($ancestors)-1;
    $parent = $ancestors[$root];
} else {
    $parent = $post->ID;
}
wp_list_pages("title_li=&child_of=" . $parent . "&echo=0");
/**
 * PAGE SIDEBAR
 * Loads all of the children of the top-level page in the current section.
 */
global $post;
if ($post->post_parent)	{
	$ancestors = get_post_ancestors($post->ID);
	$root = count($ancestors)-1;
	$parent = $ancestors[$root];
} else {
	$parent = $post->ID;
}
wp_list_pages("title_li=&child_of=" . $parent . "&echo=0");

Short and sweet: figure out the root ancestor of the current page, no matter how many levels deep we are, and then list out all the subpages of that page. This way, when browsing pages inside of a section, the interior navigation remains static so you don’t lose your sense of place. When generating these pages, it gives you current_page_item, current_page_parent and current_page_ancestor classes to allow you to style the current page differently in the list, such as by changing the background color or adding an arrow icon.

The wp_list_pages() function allows you to specify a custom post type via the post_type parameter, so we can reuse the code above by just adding &post_type=juicebox to the wp_list_pages() query string.

And it works, except for one small issue: when listing custom post types instead of pages, it doesn’t add any current_ classes to the list, so there’s no way to style the list to indicate the current page you’re on.

This is because the wp_list_pages() function has a tiny bit of code that restricts the “current page” detection to pages only. I’m guessing this is because the WP developers don’t want to assume that you want a current_page class added to a non-page. But to me, if you’re using the wp_list_pages() function then you’re okay with your custom post type being treated as a page. And they don’t replace it with anything—for instance, current_juicebox_item, where juicebox is the name of your custom post type—which is a problem.

So let’s fix it. The method we’ll use is pretty arcane, involving the use of walkers which is somewhat poorly documented by WordPress. But don’t worry about understanding it—you came here for a block of code that you can copy and paste to fix all of your problems, and that’s what you’ll get! (See the code comments for explanations of what’s going on.)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// Walker to call when using wp_list_pages with a custom post type.
// Adds current_page_ CSS classes correctly even on non-pages.
class Walker_CPT extends Walker_Page {
    function start_el($output, $page, $depth, $args, $current_page) {
        global $post;
 
        // The $current_page variable doesn't get set if we're not
        //   on a standard page, so we'll set it here before 
        //   generating the list.
        // Check is_singular() first so it doesn't give false
        //   positives on archive pages.
        if (is_singular()) $current_page = $post->ID;
 
        // Run the original start_el() function after setting the
        //   $current_page variable
        parent::start_el($output, $page, $depth, $args, $current_page);
    }
}
// Walker to call when using wp_list_pages with a custom post type.
// Adds current_page_ CSS classes correctly even on non-pages.
class Walker_CPT extends Walker_Page {
	function start_el($output, $page, $depth, $args, $current_page) {
		global $post;

		// The $current_page variable doesn't get set if we're not
		//   on a standard page, so we'll set it here before 
		//   generating the list.
		// Check is_singular() first so it doesn't give false
		//   positives on archive pages.
		if (is_singular()) $current_page = $post->ID;

		// Run the original start_el() function after setting the
		//   $current_page variable
		parent::start_el($output, $page, $depth, $args, $current_page);
	}
}

Put this in your theme’s functions.php. Now, when calling the wp_list_pages() function with a custom post type, add 'post_type' => 'juicebox' (where juicebox is the name of your custom post type) and 'walker' => new Walker_CPT() to the parameters. Note that by adding the walker parameter, we’ll have to switch to passing in our arguments as an array rather than the shorthand “query string” style. The single wp_list_pages() line in the sidebar code from above would change to this:

1
2
3
4
5
6
7
8
9
$args = array(
    'post_type' => 'juicebox',
    'walker' => new Walker_CPT(),
    'orderby' => 'menu_order',
    'order' => 'ASC',
    'title_li' => '',
    'echo' => 0
);
$children = wp_list_pages($args);
$args = array(
	'post_type' => 'juicebox',
	'walker' => new Walker_CPT(),
	'orderby' => 'menu_order',
	'order' => 'ASC',
	'title_li' => '',
	'echo' => 0
);
$children = wp_list_pages($args);

And there you have it: a sidebar that lists custom post types as though they were pages. You can do more advanced stuff with walkers, including adding current_juicebox_item instead of current_page_item as mentioned above, but I’ve never had a use case for separate classes so it keeps things neater this way.