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  /  June 2  /   142

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

Subscribe to this weekly newsletter to receive the latest blog posts by email.I respect your privacy. Your email is safe with me.

Comments 142

  • 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 Fedorov August 17, 2016 at 17:15

    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).

      • Great pluggin Misha,
        I would like to implement pagination or load more button. Pls can you point me out?

        Kind Regards Andrija

        • MishaAuthor May 29, 2017 at 18:07

          Hello,

          Yes, I recommend you to look at this post.

        • Andrija Nikolić May 29, 2017 at 18:14

          Thanks for quick replay. I have checked it bit dont know how to combine it with this post and already filtered data. Your tuts about filtering works perfect but cant figure this missing puzzle

        • MishaAuthor May 30, 2017 at 00:16

          You’re not the first who are asking about that, maybe I will write a separate detailed tutorial soon.

          But the answer is too long to leave it in comments, sorry. Just check blog updates from time to time.

    • MishaAuthor June 6, 2017 at 14:45

      Hello everyone,

      The new post about Filters + Load More is ready. I hope it will be helpful.

  • 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.nl December 14, 2016 at 12:41

        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?

    • MishaAuthor March 15, 2017 at 09:12

      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?

        • MishaAuthor March 16, 2017 at 01:17

          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?

          • MishaAuthor March 17, 2017 at 09:36

            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

          • MishaAuthor May 5, 2017 at 19:33

            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?

    • MishaAuthor March 17, 2017 at 16:25

      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

    • MishaAuthor March 18, 2017 at 01:15

      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 Negi April 21, 2017 at 14:32

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

    • MishaAuthor April 21, 2017 at 16:21

      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!!!

    • MishaAuthor May 4, 2017 at 12:11

      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.

  • CarlosRGL May 4, 2017 at 12:04

    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??

    • MishaAuthor May 17, 2017 at 00:43

      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,
        		),
        	);
        • MishaAuthor May 17, 2017 at 10:01

          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?

    • MishaAuthor May 20, 2017 at 14:23

      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.
      );
  • Abhishek May 21, 2017 at 20:01

    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

    • MishaAuthor May 23, 2017 at 09:00

      Hi Jose,

      actually there are two ways – you can use $_GET parameters in URL or a hash.
      So, ?post_tp=Horror,Comedy,Documentary or #post_type=Horror,Comedy,Documentary.

      When the filter page is loading, you’re checking the $_GET['post_tp'], select the appropriate values in the form and run your custom WP_Query for those parameters.

      Second way is when you check the parameters in JavaScript window.location.hash, after that you select the appropriate values in the form and script send the AJAX request.

      P.S. I do not recommend to use post_type as a $_GET parameter name, because there are could be conflicts.

  • Hey Misha,

    Great tutorial, thanks.

    I have one question,

    by default query is rendering nothing #response is empty?

    Why is that happening?

    Tnx

    • MishaAuthor May 31, 2017 at 16:56

      Hey Petar,

      Try just echo any string in PHP function and look if there is a response. If no response – problem in your JavaScript code, if the code prints the string, the problem is in your PHP code.

      function misha_filter_function(){
      	echo 'test';
      	die();
      }
      • Hi, thanks for response. :) Figured that one out.

        But now I’m struggling with prices.
        Comparing between works fine, max only works fine, but min only just returns else statement “No posts found”.

        I’ve checked and rechecked everything several times, tried to change couple of things, but problem is persistent.

        Can you please help me to solve this?

        Thanks.

        $args = array(
                'orderby' => 'date', // we will sort posts by date
                'order'	=> $_POST['date'], // ASC или DESC
                'post_type' => 'koop',
                'posts_per_page' => -1,
            );
         
            // for taxonomies / categories
            if( isset( $_POST['statfilter'] ) ) {
                $args['tax_query'] = array(
                    array(
                        'taxonomy' => 'koop_stat',
                        'field' => 'id',
                        'terms' => $_POST['statfilter']
                    ),
                );
         
            } else {
                // get all terms in the taxonomy
                $terms = get_terms( 'koop_stat' );
                // convert array of term objects to array of term IDs
                $term_ids = wp_list_pluck( $terms, 'term_id' );
         
                $args['tax_query'] = array(
                    array(
                        'taxonomy' => 'koop_stat',
                        'field' => 'id',
                        'terms' => $term_ids
                    )
                );
            }
         
            // create $args['meta_query'] array if one of the following fields is filled
        	if( isset( $_POST['square_min'] ) && $_POST['square_min'] || isset( $_POST['square_max'] ) && $_POST['square_max'] )
        		$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['square_min'] ) && $_POST['square_min'] || isset( $_POST['square_max'] ) && $_POST['square_max'] ) {
        		$args['meta_query'][] = array(
        			'key' => 'square_meters',
        			'value' => array( $_POST['square_min'], $_POST['square_max'] ),
        			'type' => 'numeric',
        			'compare' => 'between'
        		);
        	} else {
        		// if only min price is set
        		if( isset( $_POST['square_min'] ) && $_POST['square_min'] )
        			$args['meta_query'][] = array(
        				'key' => 'square_meters',
        				'value' => $_POST['square_min'],
        				'type' => 'numeric',
        				'compare' => '>'
        			);
         
        		// if only max price is set
        		if( isset( $_POST['square_max'] ) && $_POST['square_max'] )
        			$args['meta_query'][] = array(
        				'key' => 'square_meters',
        				'value' => $_POST['square_max'],
        				'type' => 'numeric',
        				'compare' => '<'
        			);
        	}
         
            $query = new WP_Query( $args );
         
            if( $query->have_posts() ) :
         
                while( $query->have_posts() ): $query->the_post();
                    echo '<div><a href="'. get_permalink() .'">' . $query->post->post_title . '</a></div>';
                    the_field('street', $post_id);
                    echo ' - ';
                    the_field('square_meters', $post_id);
         
                endwhile;
         
                wp_reset_postdata();
         
            else :
                echo 'No posts found';
            endif;
         
            die();
        • MishaAuthor June 2, 2017 at 14:50

          It looks like the problem was in my code :)

          Now it is fixed. Check please Step 3 line 22.

          • Thank you, man.

            I really appreciate it. Tnx again.

          • Hi, Misha

            I’ve been trying to implement your edit, but there are some issues still.

            When I implement (&&) AND operator at line 22 than just minimum price works, as well as min + max filter.
            It is only reversed problem.

            Tnx

          • MishaAuthor June 3, 2017 at 14:03

            Hmm… for me everything seems OK now.

  • Hi Misha, I really appreciate the code.

    Could you please let me know where the PHP code from step 3 should be placed?

    Currently I have:
    Step 1 (form): In the relevant page template.
    Step 2 (jQuery): In a custom plugin, consisting of a PHP file and a JS file. PHP is shown below.
    Step 3 (PHP): I currently have it placed as an ‘include’ in the php of the custom plugin. However I wonder if this is what you would suggest?
    – I tried putting it directly in the page template but it didn’t work.

    Right now my custom plugin php file looks like this…

    //enqueue scripts for Page Search Filter
    	function page_search_filter() {		
     
    		$postParentID = wp_get_post_parent_id(get_the_ID()); //get the country name of the parent page  THIS IS PRETTY CLUMSY BUT THE OTHER METHOD WASN'T WORKING - MAYBE CAN IMPROVE?
     
    			if ( is_single() && get_post_type() == 'routes' && ($postParentID == 0)) { //ensures that the Misha's jQuery from 'Step 2' is only loaded on required pages
    				wp_enqueue_script('page-search-filter', plugin_dir_url( __FILE__ ) . '/js/page-search-filter.js', array('jquery'), '1.0',   true );
    			}
     
    			/*** tried placing the below include filter-function.php here, but it didn't work ***/
     
    	} //end of page_search_filter function
     
    		add_action('wp_enqueue_scripts', 'page_search_filter');
     
    		include plugin_dir_path( __FILE__ ) . '/functions/filter-function.php'; //Includes code from Misha's 'Step 3' WOULD LIKE TO KNOW.. if this should be placed somewhere else, and/or if there is a way to make it's loading conditional on the same conditions as used above

    I very sincerely appreciate your advice.

  • arghya dutta June 9, 2017 at 17:00

    default action return from admin-ajax.php

  • Maxence Barbou June 13, 2017 at 11:00

    Hi Misha,

    I’m currently working on a website for selling cars,
    I want to filter 4 input radio (type of cars) AND 2 selects (audi,bmw,ferrari.., and the 2nd select A1, A2, A3, Série1, Série2…)

    Actually, i’have only 2 selects who work together, and filter the list. But if à filter 2 selects AND input radio, the results filter only the input radio…

    Thanks for help. :-)

     
      // for taxonomies / categories
        if( isset( $_POST['types'] ) && !empty( $_POST['types'] ) ){
            $args['tax_query'] = array(
              'post_type' => 'vehicules',
              'relation' => 'AND',
                array(
                    'taxonomy' => 'types',
                    'field' => 'term_id',
                    'terms' => $_POST['types']
                )
            );
        }else{
            // get all terms in the taxonomy
            $terms = get_terms( 'types' );
            // convert array of term objects to array of term IDs
            $term_ids = wp_list_pluck( $terms, 'term_id' );
     
            $args['tax_query'] = array(
              'post_type' => 'vehicules',
                array(
                    'taxonomy' => 'types',
                    'field' => 'term_id',
                    'terms' => $term_ids
                )
            );
        }
     
     
     
      // for taxonomies / categories
        if( isset( $_POST['marques'] ) && !empty( $_POST['marques'] ) ){
            $args['tax_query'] = array(
              'post_type' => 'vehicules',
              'relation' => 'AND',
                array(
                    'taxonomy' => 'marques',
                    'field' => 'term_id',
                    'terms' => $_POST['marques']
                )
            );
        }else{
            // get all terms in the taxonomy
            $terms = get_terms( 'marques' );
            // convert array of term objects to array of term IDs
            $term_ids = wp_list_pluck( $terms, 'term_id' );
     
            $args['tax_query'] = array(
              'post_type' => 'vehicules',
                array(
                    'taxonomy' => 'marques',
                    'field' => 'term_id',
                    'terms' => $term_ids
                )
            );
        }
     
     
     
          // Modèle
          if( isset( $_POST['modeles'] ))
            $args['meta_query'][] = array(
              'post_type'		=> 'vehicules',
              'meta_query'	=> array(
                  array(
                    'key'		=> 'Famille',
                    'value'		=> $_POST['modeles'],
                    'compare'	=> 'LIKE'
                  )
                )
              );
    • MishaAuthor June 14, 2017 at 09:19

      Hi :)

      oh, it is required time to understand your code. But I can give you a very simple recommendation for debugging.

      Before you run WP_Query try to print_r() its $args parameters. And show the result in your browser console or just inside page HTML.

      So, if you know how tax_query and meta_query parameters work, you will easily find where is the error.

  • Hi Misha,

    Awesome filter plug-in. It is doing exactly as I need.
    Is there a way to reset the filter, without reloading the page?

    Regards,
    Rafael

    • MishaAuthor June 17, 2017 at 10:56

      Hi Rafael,

      Well, ok, the first step is creating a button or link element with the ID #reset for example.

      Second step is jQuery code, something like this:

      $('#reset').click(function(){
      	$('.field1').val(''); // empty each field value
       
      	$('#response').empty(); // empty the container with posts
      	return false;
      });
      • Hello Misha,

        Thank you, but sorry I wasn’t fully clear on my question earlier.

        I have used this tutorial now to filter posts based on categories in WordPress.
        At first it displays all the posts from all categories and the filter then displays posts only from a certain category.

        <form action="<?php echo site_url() ?>/wp-admin/admin-ajax.php" method="POST" id="filter">
        		<?php
        			if( $terms = get_terms( 'category', 'orderby=name&exclude=-1' ) ) :
        				echo '<div id="select-wrapper"><select name="categoryfilter"><option>Filter op genre</option>';
        				foreach ( $terms as $term ) :
        					echo '<option value="' . $term->term_id . '">' . $term->name . '</option>';
        				endforeach;
        				echo '</select></div>';
        			endif;
        		?>
        	<input type="hidden" name="action" value="myfilter">
        	</form>
        // Filter //
        	$('#filter').change(function(){
        		var filter = $('#filter');
        		$.ajax({
        			url:filter.attr('action'),
        			data:filter.serialize(),
        			type:filter.attr('method'),
        			beforeSend:function(xhr){
        				filter.find('button').text('Zoeken naar concerten...');
        			},
        			success:function(data){
        				filter.find('button').text('Filteren');
        				$('#concert-list').html(data);
        			}
        		});
        		return false;
        	});

        Is it also possible to refresh / reload the filter so all posts from all categories are displayed again, and the filter is set back to the default selection?

        Thank you

        • By the way,

          I have found a solution to “reset” the dropdown button. If I could also reset to show all the posts, as before selecting a category, that would be perfect.

          $("#refresh").click(function() { 
                    document.getElementById('filter').reset(); 
              });
          • I have just re-checked the code and noticed I was missing a RETURN FALSE;
            It works now!

        • MishaAuthor June 20, 2017 at 14:56

          After resetting the filter if you want to display posts from all categories, you can just resubmit the form.

          $('#filter').submit();
          • Hello Misha,

            I must be missing something, because when I click the “refresh” button all posts are displayed, but with /wp-admin/admin-ajax.php as URL and no styling anymore.

            // Reset FILTER //	
            	$("#refresh").click(FUNCTION() { 
            		document.getElementById('filter').reset(); 
            		$('#concert-list').append();
            		$('#filter').submit(); RETURN FALSE;
                });
          • Sorry,

            I was to fast replying. Managed to get it up working now.

            Thank you very much for this tutorial!

          • MishaAuthor June 20, 2017 at 16:44

            Ok, great!

          • Hi Misha,

            Just noticed that it does work, but only in Firefox and Safari.
            If you could, can you see what I’m missing here? As only on Chrome it is not working…

            // Reset Filter //		
            	$("#refresh").click(function() {
            		document.getElementById('filter').reset(); 
            		$('#concert-list').append();
             
            		var filter = $('#filter');
             
            		$.ajax({
                  		url:filter.attr('action'),
                  		type:filter.attr('method'),
            			data:filter.serialize(),
                  		success:function(data){
                     		$('#concert-list').html(data);
                   		}
                	});	
            	});

            You can also check it on letsmosh.nl

          • MishaAuthor June 20, 2017 at 18:36

            Hi Rafael,

            How about refreshing your browser cache?

          • I have just re-checked the code and noticed I was missing a RETURN FALSE;
            It works now!

  • Good day, Misha! I try to filter posts by author, date and 2 specific categories. On left side category 1, on right side category 2. Here is the code

    <form action="<?php echo site_url() ?>/wp-admin/admin-ajax.php" method="POST" id="filter">
    	<?php echo ml_author_dropdown()?>
    	<label>
    	<input type="radio" name="date" value="ASC" /> Date: Ascending
    </label>
    <label>
    	<input type="radio" name="date" value="DESC" selected="selected" /> Date: Descending
    </label>
    	<button>Filter</button>
    	<input type="hidden" name="action" value="myfilter">
            <input type="hidden" name="mycat" value="knigi-stati-gum" />
    </form>
    <div id="response"></div>
    function misha_filter_function(){
    	$args = array(
    		'orderby' => 'date', // we will sort posts by date
    		'order'	=> $_POST['date'], // ASC или DESC
    		'author' => $_POST['author'],
    		'category_name' => $_POST['mycat']
    	);
     
     
     
    	$query = new WP_Query( $args );
     
     
    	if( $query->have_posts() ) :
    		while( $query->have_posts() ): $query->the_post();
    			get_template_part( '/framework/blocks/loop/loop4', get_post_format() );   
    		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');
    // END ENQUEUE PARENT ACTION

    With that code i get posts from one category but i need to add posts from second category on same page too and i cant understand what i should use. Big thanks!

  • add_action( 'restrict_manage_posts', 'ml_author_dropdown' );
    function ml_author_dropdown()
    {
    	$args = array(
    		'show_option_all' => __( 'All authors' ),
    		'name' => 'author',
    	);
    	if ( isset( $_GET['author'] ) ) {
    		$args[ 'selected' ] = $_GET[ 'author' ];
    	}
    	wp_dropdown_users( $args );
    }
    add_filter( 'parse_query', 'ml_filter_posts_by_author' );
    function ml_filter_posts_by_author( $query )
    {
    	if ( is_admin() && isset( $_GET[ 'author' ] ) && $_GET[ 'author' ] != 0 ) {
    		$query->query_vars[ 'author' ] = $_GET[ 'author' ];
    	}
    	return $query;
    }
  • Main problem is how to show category 1 on left side of page and category 2 on right. If i will add second category to filter it will show posts from both.

    • Done! Here is the code

      <form action="<?php echo site_url() ?>/wp-admin/admin-ajax.php" method="POST" id="filter3">
      							<?php echo ml_author_dropdown()?>
      							<select name="date">
      							<option value="ASC">Date: from to</option>
      							<option value="DESC" selected="selected">Date: from to</option>
      							</select>
      							<button>Filter</button>
      							<input type="hidden" name="action" value="myfilter3">
      						</form>
      						<div id="response3">
      								<div class="mycol">
      									<h2>Category</h2>
      									<?php   $args7 = array(
      											'cat' => array (187),
      									        'posts_per_page' => -1
      											);
       
      											// The Query
      											$the_query = new WP_Query( $args7 );
       
      											// The Loop
      											if ( $the_query->have_posts() ) {
      												while ( $the_query->have_posts() ) {
      													$the_query->the_post();
      													get_template_part( '/framework/blocks/loop/loop1', get_post_format() );
      												}
      												/* Restore original Post Data */
      												wp_reset_postdata();
      											} else {
      												// no posts found
      											}?>
       
      								</div>
      								<div class="mycol2">
      									<h2>Category</h2>
      									<?php   $args8 = array(
      											'cat' => array (188),
      											'posts_per_page' => -1
      											);
       
      											// The Query
      											$the_query = new WP_Query( $args8 );
       
      											// The Loop
      											if ( $the_query->have_posts() ) {
      												while ( $the_query->have_posts() ) {
      													$the_query->the_post();
      													get_template_part( '/framework/blocks/loop/loop1', get_post_format() );
      												}
       
      												/* Restore original Post Data */
      												wp_reset_postdata();
      											} else {
      												// no posts found
      											}?>
       
      								</div>
       
      						</div>
      add_action( 'restrict_manage_posts', 'ml_author_dropdown' );
      function ml_author_dropdown()
      {
      	$args = array(
      		'show_option_all' => __( 'All authors' ),
      		'name' => 'author',
      	);
      	if ( isset( $_GET['author'] ) ) {
      		$args[ 'selected' ] = $_GET[ 'author' ];
      	}
      	wp_dropdown_users( $args );
      }
      add_filter( 'parse_query', 'ml_filter_posts_by_author' );
      function ml_filter_posts_by_author( $query )
      {
      	if ( is_admin() && isset( $_GET[ 'author' ] ) && $_GET[ 'author' ] != 0 ) {
      		$query->query_vars[ 'author' ] = $_GET[ 'author' ];
      	}
      	return $query;
      }
       
      function misha_filter_function3(){
      	$args = array(
      		'orderby' => 'date', // we will sort posts by date
      		'order'	=> $_POST['date'], // ASC или DESC
      		'author' => $_POST['author'],
      		'posts_per_page' => -1,
      		'cat' => array (187)
      	);
          $args2 = array(
      		'orderby' => 'date', // we will sort posts by date
      		'order'	=> $_POST['date'], // ASC или DESC
      		'author' => $_POST['author'],
      		'posts_per_page' => -1,
      		'cat' => array (188)
      	);
       
       
      	$query = new WP_Query( $args );
       
      	if( $query->have_posts() ) :
      	echo '<div class="mycol">';
      	echo '<h2>Category</h2>';
      		while( $query->have_posts() ): $query->the_post();
      			get_template_part( '/framework/blocks/loop/loop1', get_post_format() );	
      		endwhile;
      		wp_reset_postdata();
       
      	else :
      	    echo '<div class="mycol"><h2>Caregory</h2><h4>Nothing</h4></div>';
      	endif;
      	echo '</div>';
       
      	$query2 = new WP_Query( $args2 );
       
      	if( $query2->have_posts() ) :
      	echo '<div class="mycol2">';
      	echo '<h2>Category</h2>';
      		while( $query2->have_posts() ): $query2->the_post();
      			get_template_part( '/framework/blocks/loop/loop1', get_post_format() );	
      		endwhile;
      		wp_reset_postdata();
       
      	else :
      	    echo '<div class="mycol"><h2>Category</h2><h4>Nothing</h4></div>';
      	endif;
      	echo '</div>';
       
      	die();
      }
       
       
      add_action('wp_ajax_myfilter3', 'misha_filter_function3'); 
      add_action('wp_ajax_nopriv_myfilter3', 'misha_filter_function3');
  • 16:32:07.067 jquery.js?ver=1.12.4:4 POST http://localhost:3000/wp-admin/admin-ajax.php 500 (Internal Server Error)

    I’m always getting a 500 error. Any ideas?

    • MishaAuthor June 25, 2017 at 10:27

      Hi Mike,

      The error is somewhere in your PHP code. You can try just to echo 'test' and look if anything will change.

      • I found that the line in your code causing the 500 error is (I cloned your code without customizations):

        $query = new WP_Query($args);

        Any ideas?

        Thanks so much for your help!!

      • I think I figured it out. I was using the Sage 9 theme, which has namespacing. So I had to add a \ to make the function call look like: $query = new \WP_Query($args);

        It works perfectly. Thank you so much for sharing this functionality!

  • Misha thanks for sharing this! your posts are so amazing! Keep it up! <3

  • Hi, Misha!
    Thanks for your code!

    Pls, give me direction where to dig:

    I have custom field “services” and multiple values (“cafe”, “sauna”, “kidclub”, etc).

    So I want in form to put:

    services:
    [checkbox] cafe
    [checkbox] sauna
    [checkbox] kidclub

    How to filter posts, which have checked checkboxes (meta_values) of one meta key?

    • I did in functions.php:

      $args['meta_query'][] = array(
         'key' => 'services',
          'value' => array('cafe', 'restaurant','kidclub'),
          'compare' => 'IN'
        );

      and in form:

      <input type="checkbox" name="restaurant" /> restaurant
      <input type="checkbox" name="cafe" /> кафе cafe

      But get «No posts found»

      Where did I mistake? :((((

    • It seems I found solutions, maybe for someone it will be helpful

      if( isset( $_POST['services'] )  )
      $args['meta_query'][] = array(
      'key' => 'services',
      'value' => $_POST['services'],
      'compare' => 'LIKE'
      );

      and add

      || isset( $_POST['services']

      in

      // 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' || isset( $_POST['services'] )

      form:

      <input type="checkbox" name="wpcf-services" value="restaurant" /> restaurant
      <input type="checkbox" name="wpcf-services" value="cafe" /> cafe
      • 'compare' => 'LIKE'

        showing posts with at least 1 specified meta value…

        It should be

        'compare' => 'IN'

        but with ‘IN’ it shows nothing (((

    • MishaAuthor July 6, 2017 at 10:07

      Hi Sonica,

      Your code looks good for radio buttons, not checkboxes.

      Each checkbox should have different name attribute. And remove checkbox value attribute, let it be default on

  • Hey! Thank you for your wonderful code! Such a very good work.
    How can I filter multiple taxonomies with checkboxes?

    For example:

    // for taxonomies / categories
    	if( isset( $_POST['categoryfilter'] ) )
    		$args['tax_query'] = array(
    			array(
    				'taxonomy' => 'tax_1',
    				'field' => 'id',
    				'terms' => $_POST['categoryfilter']
    			),
                            array(
    				'taxonomy' => 'tax_2', 
    				'field' => 'id',
    				'terms' => $_POST['categoryfilter']
    			),
     
    		);
    • MishaAuthor July 19, 2017 at 11:01

      Hey Alex,

      It will be something like that :)

      // first of all create empty tax_query array 
      $args['tax_query'] = array();
       
      // for EACH taxonomy create an empty array 
      $terms1 = array();
       
      // for EACH checkbox in EACH taxonomy do this
      if( $_POST['tax_1_term_1'] == 'on' ) {
      	$terms1[] = TERM_1_ID;
      }
       
      // for EACH taxonomy add it to the tax query
      $args['tax_query'][] = array(
      	'taxonomy' => 'tax_1', 
      	'field' => 'id',
      	'terms' => $terms1
      );
  • Suppose i would like to filter, in taxonomy template, the posts related with child-term of a parent term. Suppose a parent term is geometry and child-term are analytic-geometry and euclid-geometry and the filter are
    “analytic-geometry”, “euclid-geometry” and “all” (which should display all posts with term geometry)

    The problem is in all option. The code is the following

    <form id="misha_filters" action="<?php echo site_url() ?>/wp-admin/admin-ajax.php" method="POST"><?php 
    	$taxonomy  = get_queried_object();
        $taxonomy_name = $taxonomy->taxonomy; 
        $term_id = $taxonomy->term_id;
     
    	$termchildren = get_term_children( $term_id, $taxonomy_name );
    	$term_ids = wp_list_pluck( $termchildren, 'term_id' );
    	if ( ! empty( $termchildren ) && ! is_wp_error( $termchildren ) ){     
    	// set option display all option to diplay all child term related with $term_id which is parent term
    	echo'<label>
    	<input type="radio" name="categoryfilter" value="all" id="'.$term_id.'" checked="checked" /> All
    	</label>';
    	foreach ( $termchildren as $child ) :   // display all child-term as option of radio button
    	$term = get_term_by( 'id', $child, $taxonomy_name );
    	echo'<label>
    	<input class="filter" type="radio" name="categoryfilter" value="' . $term->term_id . '" />
    	</label>';				
    	}            
    ?>
    <!-- required hidden field for admin-ajax.php -->
    	<input type="hidden" name="action" value="mishafilter" />
    </form>
    <?php
    function misha_filter_function(){
     
        // filter posts related with child-term which id is in value of radio button, that is $_POST['categoryfilter']
    	if( isset( $_POST['categoryfilter'] ) && $_POST['categoryfilter'] != 'all' )
    		$args['tax_query'] = array(
    			array(
                    'taxonomy' => 'lesson_cat',
                    'field' => 'id',
                    'terms' => $_POST['categoryfilter']
                )
    		);
     
     
    	// if radio is set to "all" value display all child-term of parent term which id is 5	
        if( isset( $_POST['categoryfilter'] ) && $_POST['categoryfilter'] == 'all' ){
            // get all child-term of parent term
    	    $terms = get_terms( 'lesson_cat', array( 'child_of' => 5 ) );
     
    		// !!!!! I would like hook $term_id and have the following code:
    		//      $terms = get_terms( 'lesson_cat', array( 'child_of' => $term_id ) );
     
            $term_ids = wp_list_pluck( $terms, 'term_id' );
     
            $args['tax_query'] = array(
                array(
                    'taxonomy' => 'lesson_cat',
                    'field' => 'id',
                    'terms' => $term_ids
                )
            ); 
        }
     
    ?>

    As there are many parent term in taxonomy (geometry, algebra, analysis, etc. ) with their child-term, my question is: is there a way to pass the parent term id to misha_filter_function()?

    • MishaAuthor July 24, 2017 at 10:40

      Hi Alessio, hope you’re well,

      I do not understand exactly what you want but I can give you some suggestions.

      1. Do not use a separate checkbox for show all. It’d be better to have two links Show All and Hide All,
        so when you click them, the checkboxes will be checked/unchecked in JavaScript – it will simplify your PHP code.
      2. Parent/Child dependencies I would like to do the same way I described above. When you check/uncheck a parent checkbox – all his childs will be checked/unchecked in JavaScript.

Leave your question or feedback

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