3 Steps for Creating AJAX Post Filters

AJAX Posts (or Custom Post Types) Filter for your website by the Date, by Categories (Taxonomies) or by the Custom Field Values. Ascending or Descending order direction.

August 16, 2016 / #admin-ajax.php, #Filters, #Without Plugins

So many people asks me about post filters. So I decided to write a simple post about it — so everyone could understand how it works. 

In this post I will show you how to create an asynchronous filter by yourself, which allows to filter posts by taxonomy terms, meta values and sort results by date.

Step 1. Everything begins with the HTML form #

Our filter form will consist of 4 parts. I’ll describe each part of the form separately.

I want to filter posts by taxonomy terms #

First part of the form is a dropdown <select> of taxonomies. To create that <select> you may freely use get_terms() function. This function can work not only with custom taxonomies but with default categories and tags as well.

if( $terms = get_terms( 'category', 'orderby=name' ) ) : // to make it simple I use default categories
	echo '<select name="categoryfilter"><option>Select category...</option>';
	foreach ( $terms as $term ) :
		echo '<option value="' . $term->term_id . '">' . $term->name . '</option>'; // ID of the category as the value of an option
	endforeach;
	echo '</select>';
endif;

I want to filter posts by custom field values as well #

I use range of prices. In our case price is a custom field value that is stored in wp_postmeta table under the _price key in database.

<input type="text" name="price_min" placeholder="Min price" />
<input type="text" name="price_max" placeholder="Max price" />

By date: Ascending or Descending #

Simple radio buttons will help us to sort posts in ascending or descending order.

<label>
	<input type="radio" name="date" value="ASC" /> Date: Ascending
</label>
<label>
	<input type="radio" name="date" value="DESC" selected="selected" /> Date: Descending
</label>

Actually the featured image is just an attachment ID that is stored like a custom field value under _thumbnail_id key. We will just check that it exists.

<label>
	<input type="checkbox" name="featured_image" /> Only posts with featured image
</label>

Complete form code #

You may skip all the previous field description and use the code below as is. Insert it anywhere you want the filter to be.

<form action="<?php echo site_url() ?>/wp-admin/admin-ajax.php" method="POST" id="filter">
	<?php
		if( $terms = get_terms( 'category', 'orderby=name' ) ) : // to make it simple I use default categories
			echo '<select name="categoryfilter"><option>Select category...</option>';
			foreach ( $terms as $term ) :
				echo '<option value="' . $term->term_id . '">' . $term->name . '</option>'; // ID of the category as the value of an option
			endforeach;
			echo '</select>';
		endif;
	?>
	<input type="text" name="price_min" placeholder="Min price" />
	<input type="text" name="price_max" placeholder="Max price" />
	<label>
		<input type="radio" name="date" value="ASC" /> Date: Ascending
	</label>
	<label>
		<input type="radio" name="date" value="DESC" selected="selected" /> Date: Descending
	</label>
	<label>
		<input type="checkbox" name="featured_image" /> Only posts with featured image
	</label>
	<button>Apply filter</button>
	<input type="hidden" name="action" value="myfilter">
</form>
<div id="response"></div>

Some comments:

  • Line #1. I use default WordPress function site_url() to get actual website URL.
  • Line #1. admin-ajax.php is the default WordPress AJAX processor. I place it into the form action attribute just for simplicity.
  • Line #23. Hidden input field with the myfilter attribute is required — this is how WordPress recognize what function to use.
  • Line #25. #response div element is the container where the code will paste the result data.

Step 2. jQuery script to Send a Request and to Receive Result Data #

In this part of the post I suppose that you know something about jQuery, at least how to include it to a website page. Here is the complete jQuery-based processing code. It will send the request when the form is submitted.

jQuery(function($){
	$('#filter').submit(function(){
		var filter = $(this);
		$.ajax({
			url:filter.attr('action'),
			data:filter.serialize(), // form data
			type:filter.attr('method'), // POST
			beforeSend:function(xhr){
				filter.find('button').text('Processing...'); // changing the button label
			},
			success:function(data){
				filter.find('button').text('Apply filter'); // changing the button label back
				$('#response').html(data); // insert data
			}
		});
		return false;
	});
});

Step 3. PHP code to Process the Request #

I think it is the most interesting part. In this part you decide how to filter the posts the best way. This code is fully based on WP_Query.

function misha_filter_function(){
	$args = array(
		'orderby' => 'date', // we will sort posts by date
		'order'	=> $_POST['date'] // ASC или DESC
	);
 
	// for taxonomies / categories
	if( isset( $_POST['categoryfilter'] ) )
		$args['tax_query'] = array(
			array(
				'taxonomy' => 'category',
				'field' => 'id',
				'terms' => $_POST['categoryfilter']
			)
		);
 
	// create $args['meta_query'] array if one of the following fields is filled
	if( isset( $_POST['price_min'] ) && $_POST['price_min'] || isset( $_POST['price_max'] ) && $_POST['price_max'] || isset( $_POST['featured_image'] ) && $_POST['featured_image'] == 'on' )
		$args['meta_query'] = array( 'relation'=>'AND' ); // AND means that all conditions of meta_query should be true
 
	// if both minimum price and maximum price are specified we will use BETWEEN comparison
	if( isset( $_POST['price_min'] ) && $_POST['price_min'] || isset( $_POST['price_max'] ) && $_POST['price_max'] ) {
		$args['meta_query'][] = array(
			'key' => '_price',
			'value' => array( $_POST['price_min'], $_POST['price_max'] ),
			'type' => 'numeric',
			'compare' => 'between'
		);
	} else {
		// if only min price is set
		if( isset( $_POST['price_min'] ) && $_POST['price_min'] )
			$args['meta_query'][] = array(
				'key' => '_price',
				'value' => $_POST['price_min'],
				'type' => 'numeric',
				'compare' => '>'
			);
 
		// if only max price is set
		if( isset( $_POST['price_max'] ) && $_POST['price_max'] )
			$args['meta_query'][] = array(
				'key' => '_price',
				'value' => $_POST['price_max'],
				'type' => 'numeric',
				'compare' => '<'
			);
	}
 
 
	// if post thumbnail is set
	if( isset( $_POST['featured_image'] ) && $_POST['featured_image'] == 'on' )
		$args['meta_query'][] = array(
			'key' => '_thumbnail_id',
			'compare' => 'EXISTS'
		);
 
	$query = new WP_Query( $args );
 
	if( $query->have_posts() ) :
		while( $query->have_posts() ): $query->the_post();
			echo '<h2>' . $query->post->post_title . '</h2>';
		endwhile;
		wp_reset_postdata();
	else :
		echo 'No posts found';
	endif;
 
	die();
}
 
 
add_action('wp_ajax_myfilter', 'misha_filter_function'); 
add_action('wp_ajax_nopriv_myfilter', 'misha_filter_function');

Now you know the basics and you can easily create filters like this on WordPress:

Product filter example from eBay

If you still have problems with filters, please contact me and I will help you or leave a comment below.

Only the best of WordPress

once a week, no spam

Comments 42

  • Awesome and very helpful tutorial thanks for that.. It will be more feature-able if this filter render data by iterating JSON.

  • Whether this method is effective in a multi network plugin?

  • Igor FedorovAugust 17, 2016 at 17:08

    Awesome and helpful article. Thanks, Misha!

  • Is it possible to display on the same screen?

  • I tried it several times.
    Only a title is displayed by admin-ajax.php. lol

  • Good read!
    No need for event.preventDefault();?
    $(‘#filter’).submit(function(event){
    event.preventDefault();

  • Is it possible to display all posts at first then apply a filter to those posts? I’m able to manipulate the WP_Query to display what I need, but only after I hit the “Apply Filter” button.

    Thanks in advanced.

  • Great write up, works like a charm.

    How would add a second select for a second Taxonomy?

    • Just the same like the first, duplicate the necessary lines from each part of the code but to not forget to change taxonomy name.

  • Hello! Is it possible to increase the amount of posts that return after we apply the category filter. At the moment it seems to default to 10. Maybe an ajax “load more” button would work?

    • Hi John,

      first option is creating load more button — I can not describe in two words how to do it here — maybe I will publish a post later about that.

      Second option is to change default WordPress parameter in «Settings > Reading» from 10 to another value.

      And the third option is to force posts_per_page parameter in the code in step 3 on line 2 ($args array).

  • Hi Misha,

    Thank you for this awesome tutorial. Working like a charm.
    I want to show the excerpt. When using get_the_excerpt, it is outputting my shorcode tags.
    On my normal pages the excerpt is working like it should, but i can’t get to work with your code.

    Can you help me out.

    Thanks

    • Hi, Rob,
      thank you :)

      Did you try this $query->post->post_excerpt ?

      • rob@studiobreek.nlDecember 14, 2016 at 12:12

        Yes! It is working.
        Thank you so much.

        Last question:
        Is it posible to use this for multiple posttypes?
        Do i have to create another hook?

        • Welcome!

          Try to add the post_type parameter in the $args array:

          $args = array(
          	'orderby' => 'date', // we will sort posts by date
          	'order'	=> $_POST['date'], // ASC или DESC
          	'post_type' => array( 'post_type_name_1', 'post_type_name_2' )
          );
          • Yes ! Thanks :)

            I’ve tried this one before, but it’s showing all the posttypes together.
            I have 3 pages, each with there own posttypes, so i want to filter within each one and just show the results separately on the pages.

            Is this possible?

          • Ok, I understand,

            First, depending on your page, add to the filter HTML the following:

            <!-- change "post" to a post type name -->
            <input type="hidden" name="posttype" value="post" />

            After that in the PHP $args param add:

            $args = array(
            	'orderby' => 'date', // we will sort posts by date
            	'order'	=> $_POST['date'], // ASC или DESC
            	'post_type' => $_POST['posttype']
            );
  • Thanks Misha! It worked!

  • Hi Misha,
    Thank you for this awesome tutorial.

    I try this code separately with price and date its work well. Now i’m trying to merge it but fail. Can you please help??

    	$args = array(
    		'orderby' => array ('price','date'), // we will sort posts by date
    		'order'	=> $_POST[ array ('price','date')], // ASC или DESC
    		'post_type' => array( 'mobile')
    	);
    • Hi Farooq,

      the line #3 of your code is wrong. Try to use ‘ASC’ or ‘DESC’ strings instead.

    • Hi Misha,

      I try my best but unable to solve it. In your filter ASC & DESC for date is working,

      Now i’m trying ASC & DESC for price custom field also.
      Can you please elaborate it??

      Best Regards.

  • Could you please update this with ability to have pagination and also ‘infinite loading’? I’ll be very thankful!

  • The filter work only if category is selected with price range..
    How its give output only with price range select.. and also with both category and price selected?

    • Hmm, for me it works well.

      Are you sure that you’re using correct meta field names?

      • Yes i recheck, meta field names are correct and still price range not work without non-selected category.

        • Double check line #8 in PHP, maybe you added {. If yes, remove it.

          • Hi Misha,
            The PHP which i am trying here price-range not work without category select …..Thanks

            function misha_filter_function(){
            	$args = array(
            		'post_type' => 'tab',
            		'orderby' => 'date', 
            		'order'	=> $_POST['date'] 
            	);
             
            	if( isset( $_POST['categoryfilter'] ) )
            		$args['tax_query'] = array(
            			array(
            				'taxonomy' => 'tab-cat',
            				'field' => 'id',
            				'terms' => $_POST['categoryfilter']
            			)
            		);
             
            	if( isset( $_POST['price_min'] ) && $_POST['price_min'] || isset( $_POST['price_max'] ) && $_POST['price_max'] )
            		$args['meta_query'] = array( 'relation'=>'AND' );
             
            	if( isset( $_POST['price_min'] ) && $_POST['price_min'] || isset( $_POST['price_max'] ) && $_POST['price_max'] ) {
            		$args['meta_query'][] = array(
            			'key' => 'price',
            			'value' => array( $_POST['price_min'], $_POST['price_max'] ),
            			'type' => 'numeric',
            			'compare' => 'between'
            		);
            	} else {
             
            		if( isset( $_POST['price_min'] ) && $_POST['price_min'] )
            			$args['meta_query'][] = array(
            				'key' => 'price',
            				'value' => $_POST['price_min'],
            				'type' => 'numeric',
            				'compare' => '>'
            			);
             
            		if( isset( $_POST['price_max'] ) && $_POST['price_max'] )
            			$args['meta_query'][] = array(
            				'key' => 'price',
            				'value' => $_POST['price_max'],
            				'type' => 'numeric',
            				'compare' => '<'
            			);
            	}
             
            	$query = new WP_Query( $args );
            	if( $query->have_posts() ) :
            		while( $query->have_posts() ): $query->the_post();
            			echo '<h2>' . $query->post->post_title . '</h2>';
            		endwhile;
            		wp_reset_postdata();
            	else :
            		echo 'No posts found';
            	endif;
             
            	die();
            }
             
            add_action('wp_ajax_myfilter', 'misha_filter_function'); 
            add_action('wp_ajax_nopriv_myfilter', 'misha_filter_function');
          • Hi Ali,

            strange, everything looks OK, I will double check this code when I have time (now it is a holiday period here in St.Petersburg :))

Leave your question or feedback

phpjsHTMLCSSSQLCode
Please, enter a comment
Please, enter a name
Incorrect email