Archive for May, 2010

Better Includes and Excludes for wp_list_pages

The wp_list_pages() template tag forms the navigation core of most WordPress-powered websites, but it has a few issues:

  1. The exclude_tree parameter doesn’t work. If you include a list of page ID numbers after “exclude_tree=” in your wp_list_pages() tag, all of those pages (and all of their child pages) are supposed to be excluded from the page list. However, only the first page ID actually works this way; the others are ignored.
  2. There’s no such thing as an include_tree parameter.

That second one really became a pain working on a non-profit site recently. After I turn the site over, I want the director to be able to add and move pages whenever he wants without it breaking the nice drop-down menus I worked so hard styling for him.

The Function

That’s where this handy little function comes in. It adds an include_tree parameter and solves the exclude_tree problem by taking the list of page IDs in your exclude- and include_tree parameters, finding all of the child pages (to the depth you specify), then adding those parent and child IDs to your exclude and include parameters before passing everything on to the wp_list_pages function.

function md_list_pages( $args ) {

	$defaults = array(
		'depth' => 0, 'show_date' => '',
		'child_of' => 0, 'exclude' => '',
		'title_li' => __('Pages'), 'echo' => 1,
		'authors' => '', 'sort_column' => 'menu_order, post_title',
		'link_before' => '', 'link_after' => '',
		'include' => '', 'include_tree' => '',
		'exclude_tree' => '',

	$r = wp_parse_args( $args, $defaults );

	$exclude = list_pages_tree( $r[ 'exclude' ], $r[ 'exclude_tree' ],
			$r[ 'depth' ] );
	$include = list_pages_tree( $r[ 'include' ], $r[ 'include_tree' ],
			$r[ 'depth' ] );

	$pages = wp_list_pages( 'depth=' . $r[ 'depth' ] . '&show_date='
			. $r[ 'show_date' ] . '&child_of=' . $r[ 'child_of' ]
			. '&exclude=' . $exclude . '&title_li='
			. $r[ 'title_li' ] . '&echo=0&authors=' 
            . $r[ 'authors' ] . '&sort_column=' 
            . $r[ 'sort_column' ] . '&link_before=' 
            . $r[ 'link_before' ] . '&link_after=' 
            . $r[ 'link_after' ] . '&include=' . $include );
    	echo $pages;
	} else {
    	return $pages;


function list_pages_tree( $param, $tree, $depth ) {

	// get the parent pages of the tree
	$parent_pages = get_pages( 'include=' . $tree );

	foreach ( $parent_pages as $parent ) {
		if (!empty( $param ) ) {
			$param .= ",";

		$param .= $parent->ID;

		// get the child pages of the tree
		$child_pages = get_pages ( 'child_of=' . $parent->ID
			. '&depth=' . $depth );

		foreach ( $child_pages as $child ) {
			$param .= "," . $child->ID;

	return $param;

How to Use It

Copy and paste the above functions into your functions.php file. Then replace all instances of wp_list_pages() in your template with md_list_pages(). The parameters are the same as the wp_list_pages parameters, with the addition of the include_tree parameter.

For example, I replaced this call on the non-profit site:

<?php wp_list_pages( 'depth=2&sort_column=menu_order&title_li=
			&include=5,9,11,14,2,16' ); ?>

…which only returns the included pages, with this call:

<?php md_list_pages( 'depth=2&sort_column=menu_order&title_li=
			&include_tree=5,9,11,14,2,16' ); ?>

…which returns the included pages and all of their child pages (for the drop-down menu on the site).

I realize that this method results in the template tag parameters being parsed twice, but I decided to keep wp_list_pages in the mix rather than bypass it completely because some plugins (including some on the aforementioned non-profit site) function by adding or removing actions to that template tag.

Hope this is helpful to someone else out there — I’ll be using it myself more in the future (unless WP3.0 eliminates my need for a workaround).

Update: Wow, over 350 pingbacks, and all of them spam. Thank goodness for Akismet…

Update 23 July 2011: I thought the new menu system in WordPress 3.0 pretty much killed the need for this workaround, but I guess there are still reasons to use wp_list_pages(). I’d love to hear how people are still using the function (wp_list_pages(), not my md_list_pages()) and whether they prefer it to wp_nav_menu() or not. Depending on response, I may have to rework my solution a bit to reduce the processing overhead and to weed out the bugs.