Redirect Parent Page to a Child in WordPress

The Problem

Recently, I worked on a Web site for a client that had already been developed by another agency. My task was add some extra functionality that was too complex for the original developer to implement. After taking a look at the code, I quickly understood why: the person who created their theme and setup the site was obviously a designer, not a developer.

Among some of the mistakes that were made was an over-use of plug-ins instead of taking advantage of built-in WordPress functionality. After completing my part of the project, I decided to run through the site and see what I could do to make it more efficient. While doing so, I discovered that a plug-in was being used to redirect all parent pages back to the home page. These parent pages contained no content and their only purpose was for organization. I do not normally recommend using pages for this purpose, but this is how the client wanted their site setup. I pointed out to the client that they were missing out on a great opportunity to keep their visitors engaged instead of causing them to be confused. My solution to this issue was to “point” those parent pages to a child of the clients choosing.

The Solution

WordPress Page Attributes
Figure 1

The first step to solving this problem involved going through each page (all 20 of them) and changing the Order parameter, found in Page Attributes next to the page editor, to match the arrangement of the previously configured navigation menu, starting at 0 (see Figure 1). I could have created a new meta field for this, but I figured that using an existing field was better as it would save time and money. After that task was completed, I got down to the fun part, the real “meat and potatoes”: writing the code to do the redirection. This is what I came up with:

function my_template_redirect() {
	global $post;

	if ( ! is_object( $post ) )

	// Redirect all parent pages to their first child (determined by the order
	// set in Page Attributes) if one exists
	if ( 'page' == $post->post_type && 0 == $post->post_parent ) {
		$child = get_children( array(
			'numberposts' => 1,
			'post_parent' => $post->ID,
			'post_type'   => 'page',
			'post_status' => 'publish',
			'orderby'     => 'menu_order',
			'order'       => 'ASC'
		) );

		if ( 1 == count( $child ) ) {
			wp_redirect( get_permalink( current( $child )->ID ), 301 );

add_action( 'template_redirect', 'my_template_redirect', 0 );


This solution uses the WordPress action hook template_redirect, which is executed before WordPress determines which template to use to display the requested page. First, the callback gets the $post¹ variable and verifies that it is an object. If it is not an object, then the action is terminated. Next, the post is checked to make that it is a root level page, like so:

if ( 'page' == $post->post_type && 0 == $post->post_parent ) {

If it is, the get_children() function is used to grab the first child page with an order of 0. This portion of code can also be modified to get the first child by id, date, or whatever other parameter is required, as shown in the following example:

$child = get_children( array(
	'numberposts' => 1,
	'post_parent' => $post->ID,
	'post_type'   => 'page',
	'post_status' => 'publish',
	'orderby'     => 'post_date',
	'order'       => 'DESC'
) );

After making sure that a child page was returned, its URL is retrieved by passing its id to the get_permalink() function. Last, the wp_redirect() function is used to take the visitor to that address. Note that this a permanent redirect and not temporary one, so “301″ is passed as the second parameter instead of the default “302″. Additionally, after using wp_redirect(), exit() must be called to end the script and make sure that no other code is executed.


Although this solution worked out great for my particular situation, your mileage may vary based on the requirements of your project. Changes may need to be made to the code before it will do what you need it to. You may use the provided code for whatever legal purpose you see fit provided that you include proper attribution along side a link back to this post.

Example of proper attribution:

 * The following code was found at
 * and is Copyright (c) 2013 Joseph Leedy. Used with permission.

Feel to ask me any questions or provide me with any comments and suggestions that you have by using the comment form below but please keep them on topic. Any message that contains questions that are not related to this post or multiple suspicious links will be deleted.


Theme Disabled

I have disabled my custom theme (Postremo) while I track down a bug that causes issues with Firefox. Please bear with me while I fix this issue. I also plan to make a few tweaks while I’m at it.

Big Changes Lead to a Busier Life

Looking at the list of my posts on this site, you’ll notice that they have been few and far between over the last several months. That, of course, was not my intention for this site. Instead, my original goal was to post at least once a week about the latest developments in technology, helpful programming tips, and whatever else I chose to write about. What went wrong with this plan? Nothing, per say – I have just been too darn busy.

The Big Change

In November of 2011, my dream job in which I had been working as a professional Web Developer since August of 2011 become a career when I was put on salary. Though it is nice to finally have a steady, unchanging paycheck, this promotion has meant an increase in hours from 40 per week to 45 per week. In addition, I have also been working on a lot of projects from home in order to meet deadlines, especially during those first two months (November and December).

Speaking of home, another big change that I have made in my life is where I call home. Anticipating my promotion, in October 2011 I began searching for a new place to live that was much closer to work. (I had been driving over an hour at the time.) Much of my time not spent working in October, November, and December were spent on the hunt. In late December, I finally found the place I was looking for and  I moved to Lancaster on January 1, 2012.

Plans for the Future

I do not know when I’ll be able to accomplish my goal of weekly posts, but hopefully it will be soon. I am currently sub-contracting for Bailey Brand Consulting, which is just outside of Philly, PA and I will be doing so for at least the next month or two. Once this awesome assignment is finished, I may finally have some time to return to this site. Until then, please stay tuned!

Separating Posts by Post Format in WordPress

The Problem

WordPress has supported post formats for a couple of versions now, but I rarely ever see them being used. For this reason, I have decided to start using them myself—beginning with the “status” format. According to the Codex page, “status” posts represent short messages similar to those you would post on Twitter with the same 140-character limit. When I enabled them in my theme, I noticed that they were mixed in with the regular blog posts on the blog listing page. In my opinion, these little updates deserve their own space above the other posts.

The Solution

How did I achieve my goal? At first I added the function “has_post_format()” to show only regular posts, like so:

<?php while ( have_posts() ) : the_post() ?>
<?php 	if ( ! has_post_format('status') ) : ?>
	<!-- post loop html code goes here; omitted for brevity -->
<?php	endif; ?>
<?php endwhile; ?>

I preceded this with the following code (borrowed from a post on Stack Overflow) to only show the status updates:

$args 		= array( 'post_type'	=> 'post',
			 'post_status' 	=> 'publish',
			 'order' 	=> 'DESC',
			 'tax_query' 	=> array( array( 'taxonomy' => 'post_format',
							 'field'    => 'slug',
							 'terms'    => array( 'post-format-status' ) ) ) );
$status_updates	= get_posts( $args );
if ( $status_updates ) :
<h1>Status Updates</h1>
foreach ( $status_updates as $post ) :
	setup_postdata( $post );
<div id="post-<?php the_ID() ?>" class="post">
	<div class="post-meta">
		<h3><?php the_time( get_option( 'date_format' ) . ' ' . get_option( 'time_format' ) ); ?></h3>
	<div class="post-content">
		<?php the_excerpt(); ?>
endif; ?>

If you are intimately familiar with WordPress, this code should look fairly standard to you. The key part of this code is the “tax_query” key provided to the “$args” array and its subsequent values. Post formats are stored in their own taxonomy, with the name of the format prefixed by the string “post-format-” and stored as a term. (Hint: To show other formats, add them to the array as necessary or change the current key to which ever format you want to display.)

This worked great, but I wanted to make it more universal and easier to re-use. After some trial and error, I managed to modify a portion of the code from the first example to filter out the status updates.

function my_pre_get_posts( $query ) {
	if ( $query->is_posts_page && $query->is_main_query() ) {
		$query->set("tax_query", array( array( 'taxonomy' => 'post_format',
						       'field'	  => 'slug',
						       'terms'	  => array( 'post-format-status' ),
						       'operator' => 'NOT IN' ) ) );

	return $query;
add_action( 'pre_get_posts', 'my_pre_get_posts' );

A WordPress hook, called “pre_get_posts“, intercepts the query used in the main loop for the post listing page. The “$query->is_main_query()“ is a new method introduced in WordPress 3.3 which ensures that we do not mess up any other queries being used, such as the one that retrieves the status updates. If you look closely, you’ll notice that I am re-using the previously mentioned “tax_query” argument but I have added the “operator” parameter to tell “WP_Query” that we want to ignore this specific term.


There is a downside to this method, however. By removing the status updates from the main loop, the total number of posts (including the status updates) may be greater than your “max posts per page” setting thereby throwing your pagination off. In the future, I plan to find a way around this (probably by using the “paginate_links()” function and some simple math) but to me it is not that big of a deal right now.


As always, I hope that you have found this post useful and that it has helped you in some way. If you have a question, comment, or suggestion, do not hesitate to respond to this post using the form provided here or ask your question on the WordPress StackExchange.

Please remember that the code that I have provided is for illustrative purposes only and may not be ready for use in production environments without further modification. I accept absolutely no responsibility for any damage or harm that may come to your site or anyone else’s site through your use or abuse of this code.


December 17, 2011

Just added a Christmas theme that will be up for about the next week (until 12/25 or 12/26).

I’m Dreaming of a White Halloween

Wacky Weather

Central PA’s weather this year (2011) keeps getting stranger and stranger. In the last three months, we have experienced a magnitude 5.8 earthquake (my first) back in August, record flooding in September, and this month (October) the latest twist. We awoke this morning (10/29/11) to a freak snow storm. At first, the precipitation was a mixture of rain and snow and did not stick to the ground. Soon enough, though, it turned in to the pure fluffy white stuff and began sticking to everything including the ground and more importantly the roads. If travel is necessary, now is not the time to do it, but if you absolutely can’t avoid it, please be careful out there!

Wait, What?!

I have to admit that when saw the weather report last night on the late news, I was somewhat dubious that the storm was actually going to happen. As they say, “seeing is believing” and “a picture is worth a thousand words” so I have taken a few and included them with this post.