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.

#admin-ajax.php, #noplugins  /  August 16, 2016  /   91

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 = $('#filter');
		$.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 91

  • 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 :))

        • Hello, Ali.
          I had the same problem with my custom post type category. Probably, you are already find the answer, but maybe some one else will need it

          // for taxonomies / categories
              if( isset( $_POST['event_category_filter'] ) && !empty( $_POST['event_category_filter'] )){
                  $args['tax_query'] = array(
                      array(
                          'taxonomy' => 'eab_events_category',
                          'field' => 'id',
                          'terms' => $_POST['event_category_filter']
                      )
                  );
              }else{
                  // get all terms in the taxonomy
                  $terms = get_terms( 'eab_events_category' );
                  // convert array of term objects to array of term IDs
                  $term_ids = wp_list_pluck( $terms, 'term_id' );
           
                  $args['tax_query'] = array(
                      array(
                          'taxonomy' => 'eab_events_category',
                          'field' => 'id',
                          'terms' => $term_ids
                      )
                  );
              }
  • Hi, thanks a lot for making this tutorial! A quick question, in part 3 starting from line 60, is it possible to somehow hook an existing template without re-creating all the code? For instance, I have a custom archive page that already presents the posts exactly how I want them to look. Is it possible to use this archive to show the filtered results? Not sure if I’m explaining this right :)

  • Hello Misha, nice work, works good! But would it also be possible to disable the apply button, and still filter the items the user has selected?

  • He Misha,
    thanks for this tutorial! I’ve been looking exactly for a smooth implementation like yours!
    In my case I wanted to filter categories on button/link click though. Do you have an idea how I could rewrite your code to get it working on button click? I tried adding the the regarding category name as a data attribute and then wanted to use this to pass over to the function.. well, you might already know what I’m might doing wrong.

    looking forward to hearing from you :)

  • Hello Misha, thanks! Tell me how add ajax-pagination for this filter?

  • I am wondering how hard is it to get the ajax to run on a change of the select dropdown. I have tried this using

    <select onchange="this.form.submit()>

    However, the ajax loads on a separate page, and I am not directed to the $(‘#filter’).submit(function(){…}

    Otherwise, this is very helpful!

    • Hi Daniel,

      I recommend you to implement it the following way:

      <select id="yourselect">

      The second step is jQuery:

      $('#yourselect').change(function(){
      	// AJAX request
      })
      • As someone who was struggling with this, I want to mention that I had to make an additional change for this to work:

        From this:

        var filter = $(this);

        To this:

        var filter = $('#filter');

        great stuff :)

  • Great tutorial mate, I’ve got only one problem:
    – dropdowna list is showing categories, but ajax response return only data from post_type =>post,
    I’ve set the post type to my custom post type – portfolio :

    		$args['tax_query'] = array(
    			array(
    				'taxonomy' => 'category',
    				'field' => 'id',
                        'post_type' => 'portfolio',
    				'terms' => $_POST['categoryfilter']
    			)
    		);

    But it’s still not working. Can you please help me?

    • MishaAuthorMarch 15, 2017 at 09:03

      Hi Maciek,

      thanks,

      the post_type parameter should never be inside tax_query. Add id to the $args array the following way:

      $args = array(
      	'orderby' => 'date', // we will sort posts by date
      	'order'	=> $_POST['date'], // ASC или DESC
      	'post_type' => 'portfolio'
      );
      • Awesome stuff :) Cheers! :)
        One more question – how to submit form on clicking radio button?

        • MishaAuthorMarch 16, 2017 at 01:03

          Try to replace this part of the jQuery script:

          $('#filter').submit(function(){
          	var filter = $(this);

          to this:

          $('radio.your_radio_class').change(function(){
          	var filter = $('#filter');
          • Ok thanks :) Last one thing:) Is there a way of showing in list of categories something like ‘All’? So when I click on it it’s gonna show all categories?

          • MishaAuthorMarch 17, 2017 at 09:03

            Just add to your select dropdown something like:

            <option value="">All categories</option>
          • Dear Misha

            Thanks for this amazing tuto
            I’ve tried to add this : All categories to show all results
            But it doesn’t work, it shows : nothing found.
            Do you know why ?

            Other thing
            I’d like to use the filter twice in the same page cause i have two tab with different posts i’d like to filter by ‘city terms’
            But, in the second tab, it doesn’t work with ajax but it works if i click on the button

            I’ve tried for hours but i can’t do…

            Could you help me
            Even if i understand you made a lot

            Many thanks

            Damien

          • MishaAuthorMay 5, 2017 at 19:05

            About all categories – there is somewhere a small mistake in your code, please look.

            About two filters. As an option use different IDs in HTML elements, use twice the given jQuery code with the different selectors and AJAX action parameters.

            It depends on your situation of course, but PHP code can be the same – in that case AJAX action parameter will be the same.

  • Works perfectly!

    I wanted to tweak your code to use not select but buttons. Unfortunately serialize() does only work with input and select.

    Can you help me achieve this?

    • MishaAuthorMarch 17, 2017 at 16:03

      Hi Julie,

      instead of serialize() you should pass a query string then, something like this:

      data : 'param1=value1&param2=value2&param3=value3',
  • Hi Misha,

    I use this, for my home page, and Anytime the result is “0”…

    Can you help me ?

    Thank you so mush :3

    • MishaAuthorMarch 18, 2017 at 01:03

      Hi Beifong,

      it means that ajax is ok, but it is not connected to the hook/function. Please read the post one more time and pay attention to the action hidden field and hook ( add_action() arguments ) names.

  • You are a life saver! thank you so much!

  • could you show me a demo? :)

  • Works perfect. I have been using this for many times

  • How can I trigger some jQuery after ajax is done loading?

    Thanks!

  • Of course! Worked perfect, thank you!!

  • This works for me. Thank you!

  • Ashish NegiApril 21, 2017 at 14:04

    Hi sir how i can implement it in my website can you tell me?

    • MishaAuthorApril 21, 2017 at 16:04

      Hi,

      of course, you can, if you use WordPress.

      To do it – just read this post one more time and complete every step on your own website.

  • how i use a “reset” button? thanks and excellent code!!!

    • MishaAuthorMay 4, 2017 at 12:05

      You’re welcome!

      I think you can just reload the page when the button clicked. Or set all the fields to defaults and then resend the form.

  • CarlosRGLMay 4, 2017 at 12:05

    this save my day !!!! thx you very much ;)

  • Hi Misha i tried to used the form but i have some questions, if i used checkboxes, (one checkbox for one taxonomy for example), when i select one check box the result is ok, but when i check two checkboxes (two taxonomies terms) , the resul just show the post that belog to one term??

    • MishaAuthorMay 17, 2017 at 00:05

      Hi Javier,

      did you use the same name attributes for both checkboxes?

      • Hi finally i can use the checkbox for two taxonmies (marca and edad) but now my problem is that i want display only post for a especific categories, (i working with woocommerce) my code is:

        function misha_filter_function(){
        	$args = array(
        		'orderby' => 'date', // we will sort posts by date
        		'order' => $_POST['date'], // ASC или DESC
        		'post_type' => 'product',
        		'product_cat' =>'perros', //"perros" is the parent category inside woocoommerce products
        	);
         
        	// for taxonomies / categories
        	if( isset( $_POST['marcasfilter'] ) )
        		$datosmarca = $_POST['marcasfilter'];
         
        	$datosedad = $_POST['edadfilter'];
        	$args['tax_query'] = array(
        		'relation' => 'OR',
        		array(
        			'taxonomy' => 'marca',
        			'field' => 'id',
        			'terms' => $datosmarca,
        		),
        		array(
        			'taxonomy' => 'edad',
        			'field' => 'id',
        			'terms' => $datosedad,
        		),
        	);
        • MishaAuthorMay 17, 2017 at 10:05

          Where are your specific categories in your code?

          I do not like the way how you filter taxonomies, please look, how meta_query implemented in the post, and make the same for tax_query – they are very similar.

          P.S. Please, use the buttons in editor (php, js etc) when want to insert the code in comment!

  • Hi Misha, the code only show 5 of 30 results, How can I do to show all the results?

    • MishaAuthorMay 20, 2017 at 14:05

      Hi Mark,

      try to add posts_per_page parameter to your $args array.

      $args = array(
      	'orderby' => 'date',
      	'order'	=> $_POST['date'],
      	'posts_per_page' => -1 // show all posts.
      );
  • how to filter with multiple post type

    I have tried with

    array('post_type' =>array('post1','post2','post3'))

    But In query it is showing post = ‘post1post2post3’

  • Thanks Misha! I was able to do something very similar and it worked perfectly following your tutorial.

    If you have an archive of Movies and the user selects 3 filter options (Horror, Comedy, Documentary), how would they be able to send that page with those filters selected? How would you generate a shareable link?

    Looking forward to your response!

    Thanks,
    Jose

Leave your question or feedback

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