Home > Web Design, WordPress > Better Includes and Excludes for wp_list_pages

Better Includes and Excludes for wp_list_pages

May 12, 2010

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.

  1. Ross
    May 26, 2010 at 11:48 PM

    Hey man,

    This is fantastic but the functions.php has done something to my WordPress (in the back end). I cant edit pages and some of the options (eg – SETTINGS>READING) don’t work.

    Could this be because of my wordpress version(2.9.2) or the way I’ve inserted your code into my functions.php file? I do have an existing function in my file and I just added your code.

    The function is awesome. It really works. Thanks.

  2. Ross
    May 26, 2010 at 11:58 PM

    I’m a douche bag… I inserted it into the functions.php file incorrectly.

    Thanks mike.

    • May 29, 2010 at 1:06 PM

      Hi Ross –

      I can’t believe I missed replying to my first real comment on this post!

      Glad the function is working for you. I’ve used it a couple of times now myself, though I’m hoping that it won’t be necessary anymore after version 3 is finalized.

  3. Bill Addison
    September 8, 2010 at 12:41 PM

    Michael, people like you make WordPress awesome to work with. Thanks heaps! Worked perfectly for me.

    Have you thought about submitting this to WordPress for integration into? (if that’s possible?)

  4. October 25, 2010 at 2:13 PM

    Hi Michael,

    Thank you for sharing this function with us! Perhaps like you, I found WordPress’ lack of an include_tree baffling, and it took me a while to figure out that, I wasn’t doing anything wrong, but exclude_tree is broken. So this function is really great!

    I have noticed, though, that it seems to lengthen the loading time for one of my client’s sites. I’m calling the function twice, to load 5 trees of pages. Is this function very processor intensive? If so, can you think of any way to lessent the load?


    • March 13, 2012 at 2:17 PM

      Trevor did you ever find a solution for speeding up the function? I have a delayed dropdown effect too.

  5. Ken
    December 30, 2010 at 3:46 PM

    useful post thanks. I was messing around for the past two hours trying to do something and your example gave me one of those lightbulb moments 🙂

  6. January 26, 2011 at 3:12 PM

    Great – this is just what I was looking for. Still can’t believe there isn’t an include_tree in WP.

  7. February 15, 2011 at 2:01 PM

    Great function! This was really helpful – I’m surprised that I couldn’t find this built into WordPress already. Thanks for posting.

  8. February 23, 2011 at 7:48 AM

    Hey! I think this causes a problem with exclude. When you include a tree & exclude one of it’s children it doesn’t work.

    To get past this I included passed an extra argument (pages to exclude):

    $include = list_pages_tree( $r[ ‘include’ ], $r[ ‘include_tree’ ], $r[ ‘depth’ ], $r[ ‘exclude’ ] );

    Then checked each child id as it was being entered into the results string:

    foreach ( $child_pages as $child ) {

    $id = (string)$child->ID;

    $found = strpos($exclude, $id);

    if($found === false){
    $param .= “,” . $child->ID;

    That seemed to work anyhow. 🙂

    • July 4, 2011 at 4:03 PM

      Great Michael and thanks Steve, this works for me too but I spent some time with it before I realized In addition to the code you posted I needed to declare that $exclude param you mention just before your first line of posted code.

      function list_pages_tree( $param, $tree, $depth, $exclude=” ) {…

  9. February 25, 2011 at 10:07 PM

    Thanks for this great workaround. It solved half of my menu issues – now to find a wp_list_categories with include_tree to solve the rest…

  10. June 25, 2011 at 6:13 PM

    Thank you. Thank you. Thank you.

    On the whole, WordPress works very well, but I seem to keep running into these black holes where things are half-implemented or function in very strange ways. Finding solutions for these issues is like being a needle lost in a haystack.

    Thanks for creating a solution to this issue.

  11. elvis
    July 4, 2011 at 2:50 PM

    Hi, I have this and it works great if i’m outputting directly but I wanted to change the output.
    so i made a variable and set it to md_list_pages and then did an echo of the results. Which works fine with wp_list_pages but not with md_list_pages.
    Have you tested it using echo=0 ?

  12. July 22, 2011 at 11:28 PM

    Hi elvis –

    md_list_pages is just pre-processing for wp_list_pages. It generates the include and exclude lists (since wp_list_pages doesn’t do a good job of that), but otherwise just passes all of the variables as-is to the wp_list_pages function.

    Depending on response to my update I may have to revisit this function, but in the meantime I’ve added a quick fix to address the issue you’re talking about.

  13. March 13, 2012 at 1:52 PM

    Michael, thanks for posting this great solution.

    It’s great to enable menus to use your parent/child order to automate dropdown management. Having to manually mirror your parent/child relationships in your custom menu is a nightmare when there are lots of pages. Thanks for this great elegant solution!

  14. Ciantic
    March 25, 2012 at 5:30 AM

    WP almost supports this already, I came up this:

    $root_id = SET VALUE HERE // Same as "include_tree" above

    // get children pages
    $pages = wp_list_pages(array(
    'depth' => 0,
    'title_li' => '',
    'child_of' => $root_id,
    'echo' => 0

    // root page with children
    $root = wp_list_pages(array(
    'depth' => 0,
    'title_li' => '',
    'include' => $root_id,
    'echo' => 0,
    'link_after' => ''.$pages.''

    echo $root;

  15. Ciantic
    March 25, 2012 at 5:31 AM

    Ciantic :
    WP almost supports this already, I came up this:

    *Sigh* This commenting platform rips the ul-tag from the “link_after” in the second call, add ul tag around the $pages if you want to use my code.

  1. November 15, 2011 at 1:39 AM
Comments are closed.
%d bloggers like this: