Randy Hoyt

Ordering WordPress Post Types By A Custom Field

WordPress contains two main types of content when first installed: posts (for blog, date-based content) and pages (for static, timeless content). Posts are automatically ordered in reverse chronological order by publish date, and pages are automatically ordered by a specified menu order. Those two ordering schemes make good sense for these two types of content, but they may not make sense for other types of content.

Imagine you have a web site for a university course with a custom post type for Assignments, with each assignment having a due date stored in a custom field. You would probably then want to have the list of Assignments in the WordPress Dashboard area displayed in chronological order by this due date custom field. (I discuss how to set up the custom post type for this scenario in my talk at edUi 2011, “Advanced WordPress Features & Customizations.”)

To customize the query WordPress uses to display the list of posts in this manner, you add a filter to two hooks: posts_join and posts_orderby. When you add a filter to these hooks, WordPress will pass a string into your custom function:

posts_join

Your custom filter receives the part of the query that contains the list of tables to be joined together. You then need to add to this a left join to the relevant rows in the wp_postmeta table. (You can use a subquery to get only the due date rows.) In my example, the additional join looks like this:

add_filter('posts_join', 'rrh_assignment_join' );
function rrh_assignment_join($wp_join)
{
	if(is_post_type_archive('rrh_assignment') || (is_admin() && $_GET['post_type'] == 'rrh_assignment')) {
		global $wpdb;
		$wp_join .= " LEFT JOIN (
				SELECT post_id, meta_value as date_due
				FROM $wpdb->postmeta
				WHERE meta_key =  'date_due' ) AS DD
				ON $wpdb->posts.ID = DD.post_id ";
	}
	return ($wp_join);
}

We do not want to apply this filter to every post listing, just the ones related to assignments. I have wrapped the code that adds to the string in a conditional that checks if it is the assignment archive page on the web site — is_post_type_archive(‘rrh_assignment’) — or if it is the assignment list page in the WordPress Dashboard area — is_admin() && $_GET[‘post_type’] == ‘rrh_assignment’. On every other page, the part of the query that lists out the tables to be joined together passes through unchanged.

posts_orderby

Your custom filter receives the part of the query that describes how the results should be ordered. You then need to replace this with your own ordering. In my example, the new order looks like this:

add_filter('posts_orderby', 'rrh_assignment_order' );
function rrh_assignment_order( $orderby )
{
	if(is_post_type_archive('rrh_assignment') || (is_admin() && $_GET['post_type'] == 'rrh_assignment')) {
			$orderby = " STR_TO_DATE(DD.date_due,'%m/%d/%Y') ASC ";
	}
 	return $orderby;
}

Just like before, we only want to apply this filter to the assignment archive page on the web site and on the assignment list page in the WordPress Dashboard.

Download

This article builds upon my talk at edUi 2011 on how to set up custom post types. You can view that presentation and download the necessary code to make all this work on the page for that presentation: “Advanced WordPress Features & Customizations.”