March 20, 2014

Adding Export Filters for Custom Post Types

I recently needed to export a single post for a client. I thought it through and decided that I would create a unique author, assign the post to the author, then use the Authors filter in the WordPress Export Tool to export the only post by the author. It seemed like a good plan until I realized that export filters are not available to custom post types and, as I’m sure you can now easily figure out, the post in question was in fact a custom post.

Now, I’m sure there’re some plugins out there that could’ve solved this problem for me. However, there’s a kink in my DNA or something that makes me want to figure it out myself. I suppose that’s a big part of why I do what I do for both work and leisure.

I did some searching, and it turns out that this is one those increasingly rare subjects in the WordPress universe that has very little written about it. I was finally able to find a reference to a blog post on the subject, but, when I followed it, the site was no longer up. So, I hit the Wayback Machine and retrieved the post.

Here it is as code. All commentary has been converted to comments.

/* Originally published */
When we create a custom post type with can_export set to true, 
we can export the custom post types from the admin export screen. 
When the CPT is selected, the export creates a WXR file with all
of the posts of this type. We can add filters to the custom post 
type so that a subset can be exported.
//First add the HTML markup required using the javascript.
function reviews_export_js() {
<script type="text/javascript">
	jQuery( document ).ready( function( $ ) {
		var form = $( '#export-filters' );
				ourradio = form.find( 'input:radio[value=reviews]' );
				ourradio.closest( 'p' ).after( '<ul class="u-export-filters" id="reviews-filters" style="margin-left: 18px; display: none;">\n<li>\n<label>Date range:</label>\n<?php date_filter(); ?> </li>\n</ul>\n' );
			filters = form.find( '.export-filters' );
		form.find( 'input:radio' ).change(function() {
			switch ( $( this ).val() ) {
				case 'reviews': $( '#reviews-filters' ).slideDown(); break;
				default: $( '#reviews-filters' ).slideUp(); break;
add_action( 'admin_head-export.php', 'reviews_export_js' );
We are using admin_head-export.php action hook. This is called
whenever the export screen is loaded. The method review_export_js 
adds the necessary markup. We first find the radio button 
displaying our custom post type and then append the required 
filter markup. In this case we are adding a date filter to the 
custom post type. The date_filter method just echos the markup 
for the starting and ending dates.
function date_filter() {
function s_date_filter() {
	echo '<select name="reviews_start_date">';
	echo '<option value="0">' . __( "Start Date" ) . '</option>';
	echo '</select>';
function e_date_filter() {
	echo '<select name="reviews_end_date">';
	echo '<option value="0">' . __( "End Date" ) . '</option>';
	echo '</select>';
function reviews_export_date_options() {
	global $wpdb, $wp_locale;
	$months = $wpdb->get_results( "
		SELECT DISTINCT YEAR( post_date ) AS year, MONTH( post_date ) AS month
		FROM $wpdb->posts
		WHERE post_type = 'reviews' AND post_status != 'auto-draft'
		ORDER BY post_date DESC
	" );
	$month_count = count( $months );
	if ( !$month_count || ( 1 == $month_count && 0 == $months[0]->month ) ) {
	foreach ( $months as $date ) {
		if ( 0 == $date->year ) {
		$month = zeroise( $date->month, 2 );
		echo '<option value="' . $date->year . '-' . $month . '">' . $wp_locale->get_month( $month ) . ' ' . $date->year . '</option>';
From now on, the export screen has the option of accepting the start 
date and end date fields whenever the custom post type is selected. 
Finally, we need to filter the records. We use the query filter to 
add the additional where clauses. But first, we ensure that we add 
the query filter only when export is taking place using the 
export_wp action hook.
function reviews_add_query_filter() {
	if( $_REQUEST['content'] == 'reviews' ) {
		add_filter( 'query', 'reviews_query' );
add_action( 'export_wp', 'reviews_add_query_filter' );
The reviews_query method updates the query to include the filters for
the start and end dates.
function reviews_query( $query ) {
	$args = array( 'start_date' => false, 'end_date' => false );
	if ( $_REQUEST['reviews_start_date'] || $_REQUEST['reviews_end_date'] ) {
		$args['start_date'] = $_REQUEST['reviews_start_date'] ;
		$args['end_date'] = $_REQUEST['reviews_end_date'] ;
	global $wpdb;
	if ( $args['start_date'] ) {
		$query .= $wpdb->prepare( " AND {$wpdb->posts}.post_date >= %s", date( 'Y-m-d', strtotime( $args['start_date'] ) ) );
	if ( $args['end_date'] ) {
		$query .= $wpdb->prepare( " AND {$wpdb->posts}.post_date < %s", date( 'Y-m-d', strtotime( '+1 month', strtotime( $args['end_date'] ) ) ) );
	var_export( $query );
	return $query;
You can adjust the reviews_query and date_filter methods to accept 
different filter conditions for export.

The example adds start and end date dropdowns so that you can filter a custom post type named ‘reviews’ by date. To use this with a custom post type other than reviews, you would replace every instance of reviews with the name of another custom post type.

In the use case mentioned above, I could set the published date of the single post that I needed to export to a day on which no other posts of that type were published.

A good deal of the code above was appropriated from the WordPress core at wp-admin/includes/export.php. When I get a chance, I’ll take a crack at making all of the filters available to posts and pages available to a custom post type. From there, I could see some value in making these filters available to all custom post types. I’ll follow up in a subsequent post.

Posted in: JavaScript | PHP | WordPress