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  /   359

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

← Older
  • Hello, thank you for your post! I am able to get the filter working but the results go to the url /wp-admin/admin-ajax.php

    Shouldn’t the results be posted in the on the same page as the filter?

    • Hello,

      Usually it happens because of a JavaScript error. Please check your browser console.

      • I was able to get this working. Thank you again for your post!

        One thing though. Do you happen to know a way or can you point me to some documentation to add a search to this sort / filter form?

  • Hi Misha, I know you’ve covered this a half-dozen times within these comments but after going through every comment and trying everything, I can’t get this to work properly with checkboxes (and two taxonomies). It works perfectly when using select fields or multiple select fields but the minute I try and convert it to checkboxes, not so much. It’s not throwing any javascript errors and it does load the posts. But I get every result. Like it’s not filtering correctly.

    My javascript is pulled straight from your post without changes:

    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;
    	});
    });

    And my form gets it’s contents from two custom post type taxonomies (topics and types) – Pulled from the comment section mostly:

    <form action="<?php echo site_url() ?>/wp-admin/admin-ajax.php" method="POST" id="filter">
    		<div class="col span_5_of_12">
    		<?php 
    			if( $p_topics = get_terms( array( 'taxonomy' => 'program_topic' ) ) ) :
    				foreach( $p_topics as $p_topic ) :
    					echo '<input type="checkbox" id="topic_' . $p_topic->term_id . '" name="topic_' . $p_topic->term_id . '" /><label for="topic_' . $p_topic->term_id . '">' . $p_topic->name . '</label><br>';
    				endforeach;
    			endif;
    		?>
    			</div><div class="col span_4_of_12">
    		<?php 
    			if( $p_types = get_terms( array( 'taxonomy' => 'program_type' ) ) ) :
    				foreach( $p_types as $p_type ) :
    					echo '<input type="checkbox" id="type_' . $p_type->term_id . '" name="type_' . $p_type->term_id . '" /><label for="type_' . $p_type->term_id . '">' . $p_type->name . '</label><br>';
    				endforeach;
    			endif;
    			?></div>
    			<div class="col span_3_of_12">
    		<button>Apply filter</button>
    				<input type="hidden" name="action" value="myfilter"></div>
    	</form>

    But where I think I’m struggling is the filter. There were a few comment areas covering this including one or two that use multiple taxonomies. But I think somewhere in there, trying to do the checkboxes and two taxonomies at the same time, I got a little lost:

    function misha_filter_function(){
    	$args = array(
    		'post_type' => 'programs',
    		'orderby' => 'date', // we will sort posts by date
    		'order'	=> $_POST['date'] // ASC или DESC
    	);
     
    	$terms1 = array();
     
    	if( $p_topics = get_terms( array( 'taxonomy' => 'program_topic' ) ) && $p_types = get_terms( array('taxonomy' => 'program_type') ) ) :
    	$all_terms = array();
     
    	foreach( $p_topics as $p_topic ) {
    		if( isset( $_GET['topic_' . $p_topic->term_id ] ) && $_GET['topic_' . $p_topic->term_id] == 'on' )
    			 $all_terms[] = $p_topic->slug;
    	}
     
    	foreach( $p_types as $p_type ) {
    		if( isset( $_GET['type_' . $p_type->term_id ] ) && $_GET['type_' . $p_type->term_id] == 'on' )
    			 $all_terms[] = $p_type->slug;
    	}
     
    	if( count( $all_terms ) > 0 ) {
    		$args['tax_query'] = array(
    				'taxonomy' => 'program_topic',
    				'field' => 'slug',
    				'terms'=> $all_terms
    		);
    	}
    endif;
     
    	$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');

    Any help would be greatly appreciated. I’ve been banging my head against the wall on this for days. And days and days before that with a similar method that I couldn’t get working with checkboxes (or even multiple select for that matter).
    Thanks,

    • Sorry, ignore that $terms1 array. I left something in there from another attempt.

    • Hey,

      Everything seems OK how you deal with checkboxes. But you process multiple taxonomies incorrectly. This comment covers it.

      • I saw that one but couldn’t figure out what to replace for the “name” since I’m using generated input names. The isset area and again for terms=

        if( isset( $_POST['selectbox1'] ) ) {

        Can I replace that with the same thing?

        name="topic_' . $p_topic->term_id
      • Well that name= thing didn’t work as well as a bunch of other stuff I tried. I still can’t get it working but I can confirm my tax-query is coming back empty no matter what I try. There’s nothing to check that isset against. I even tried checking for “on” and wrapping each group of checkboxes in a fieldset. I’m stumped.

  • I ditched the isset, I don’t know if that’s ok or not. If I manually set the terms, it does recognize them. But I still can’t figure out how to get the checkbox inputs to pass from the post.

    function filter_function(){
    	$args = array(
    		'post_type' => 'programs',
    		'orderby' => 'date', // we will sort posts by date
    		'order'	=> $_POST['date'] // ASC или DESC
    	);
     
    	$args['tax_query'][] = array(
    		'taxonomy' => 'program_topic',
    		'field' => 'slug',
    		'terms' => $_POST['program_topic']
    	);
     
    	$args['tax_query'][] = array(
    		'taxonomy' => 'program_type',
    		'field' => 'slug',
    		'terms' => 'program_type'
    	);
     
     
     
    	$query = new WP_Query( $args );
     	print_r($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', 'filter_function'); 
    add_action('wp_ajax_nopriv_myfilter', 'filter_function');
  • multiple taxonomies not working properly.

    	if( isset( $_POST['categoryfilter'] ) )
                $args['tax_query'] = array(
                    array(
                        'taxonomy' => 'job-categories',
                        'field' => 'id',
                        'terms' => $_POST['categoryfilter']
                    )
                );
            if( isset( $_POST['typefilter'] ) )
                $args['tax_query'] = array(
                    array(
                        'taxonomy' => 'job-types',
                        'field' => 'id',
                        'terms' => $_POST['typefilter']
                    )
                );
            if( isset( $_POST['locationfilter'] ) )
                $args['tax_query'] = array(
                    array(
                        'taxonomy' => 'job-location',
                        'field' => 'id',
                        'terms' => $_POST['locationfilter']
                    )
            );
    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();
    • Hey,
      It is just because your code is completely incorrect.

      The solution was already discussed in comments, please look.

  • hello, its working fine.awesome work but i have one issue.the issue is that i am filtering taxonomy term and subterm and its sub term but when meta query added to array that time i have to select all dropdown then after its filtering.

    • Hello,
      I’m not sure if I understand you correctly, but maybe it is because you have to set empty value attribute for the first option in dropdown.

      <option value="">Select terms</option>

Leave your question or feedback

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