Current section navigation in WordPress

UPDATE (4/1/09): We’ve released a WordPress plug-in based on this post that makes adding top level navigation to your WordPress site a breeze. Download it now from the official WordPress plug-in repository!

Increasingly, we’re building sites – like our own – that are completely managed by WordPress. WordPress makes it very easy to output a list of pages using the wp_list_pages function; you can control depth levels, surrounding tags, and even explicitly exclude certain pages. And with the precise classes that accompany the output HTML elements, you can finely control the output with stylesheets.

Many sites, also like ours, list pages only under the current top level page (or, put another way, pages in the current section) in a sidebar. For example, if you go to the Services page, or any of the pages inside that section, you’ll notice a box atop the right sidebar called “More in Services” that provides navigation within the Services section.

Taking advantage of the wp_list_pages function to create section-based navigation for sites with two levels of page depth is a pretty straightforward matter, as I’ll explain. But how do we efficiently output section based navigation for sites with 3 or more levels of page depth?

The wp_list_pages function allows developers to list subpages (also known as “child” pages) of a given page through a “child_of” paramater that accepts a page ID. So, for example, our Services page has an ID of “4”. If we want to list only the subpages of the Services page, without a heading, we can use the following call:


If we want to automatically list the children of the current page, we can call the post ID, and write the function like so:


What if we’re on a child page? How do we list its siblings? Fortunately, WordPress provides an easy method to get a page’s parent ID. Here’s the answer:


But how do we know if need to show siblings or children? Let’s assume for the moment we only have two levels of pages, as this site has: top level pages, i.e. Services, and subpages, i.e. Areas of Focus. All we have to do is check whether the page actually has a parent, and then choose our function accordingly.

if ($post->post_parent) {
} else {

In most real world scenarios you’ll probably want to confirm that there are actually pages to list (you can check whether wp_list_pages actually returns a value if you add “echo=0″ to the parameters) and that we’re actually on a page (is_page()). But we won’t get into that now.

But what about sites with 3 levels (or more) of pages? Just yesterday I was developing a template for a client that called for a section navigation in the sidebar; but this site had at least 3 page levels. The idea was to show all subpages within the current section, no matter what level of page depth.

There were, of course, a few ways to approach the challenge. One could, for instance, output the entire navigation again, and use the precise  element classes (i.e. “current_page_item”) to only show the appropriate pages. But that seemed wasteful, and the redundant information probably not ideal for search engine optimization.

As it turns out, WordPress just recently (as of version 2.5) added a barely documented get_post_ancestors function that, when passed a page, will return an array with the ancestory of that page, going back as many levels (pages) as necessary, with each page’s ID along the way. The last item in the array is the (drumroll) top level page.

A little basic PHP array code, and you can quickly determine whether you’re actually on a sub page (at any level), pull out the top level page ID if so, and pass it to the wp_list_pages function to get the desired section navigation. You can also get the title of the top level page, a.k.a the section you’re in.

$post_ancestors = get_post_ancestors($post);
if (count($post_ancestors)) {
 $top_page = array_pop($post_ancestors);
 $children = wp_list_pages('title_li=&child_of='.$top_page.'&echo=0');
 $sect_title = get_the_title($top_page);
} elseif (is_page()) {
 $children = wp_list_pages('title_li=&child_of='.$post->ID.'&echo=0&depth=2');
 $sect_title = the_title('','', false);
if ($children) {
 //** however you want to output your section navigation **/


UPDATE: Outputting only appropriate grand children and lower

If you read the comments below, Daniel had a great question about the display of grandchildren and lower. I’ll let his post explain the question in detail, but the general idea is that often times we only want to show grandchildren (3rd level pages or lower) if we’re inside the corresponding 2nd level page’s navigation. As I explained to Daniel, there’s an easy way to do this with CSS, which is what I had used, but if you’re determined to output, in HTML, the appropriate pages, here’s the answer:

//get the current page's ancestors
$post_ancestors = get_post_ancestors($post);

//initialize pagelist variable to hold list of pages to include
$pagelist = "";

//add the immediate children (no grandchildren) of each page
//in this page's ancestory to the list
foreach ($post_ancestors as $theid) {
 $pageset = get_posts('post_type=page&post_parent='.$theid);
 foreach ($pageset as $apage) {
 $pagelist = $pagelist.$apage->ID.",";

//add any children of the current page to the list
$pageset = get_posts('post_type=page&post_parent='.$post->ID);
foreach ($pageset as $apage) {
 $pagelist = $pagelist.$apage->ID.",";

//get the list of pages, including only those in our page list
$children = wp_list_pages('title_li=&echo=0&include='.$pagelist);

//get the top level ancestors title, aka the section title
$sect_title = get_the_title(array_pop($post_ancestors));

If you followed that, I probably don’t need to spell this out, but you’ll want to wrap that code in a more basic check or two, depending on your template (is_page(), whether it has a parent, etc) as illustrated in the previous example’s code.

As I mentioned in my comment to Daniel, this took a couple of hours to figure out. So if this helps you with a project, we’d humbly ask that you donate whatever you feel this was worth!