Back to all articles
WordPress Filter Posts with Ajax, without page-reload or plugins
Development

WordPress Filter Posts with Ajax, without page-reload or plugins

Updated on November 9, 2023

WordPress Filter without page reload using ajax is not a hard thing to do. Many developers will quickly install another plugin like facetwp or yith to do things like this. But it’s actually pretty simple, as WordPress has default ajax functionalities. For me, as a front-end developer, the hardest part is making the actual WP_Query if we’re extending this method for combined filters WooCommerce.

A more advanced ajax filter for Woocommerce will follow soon! Let’s first start with the basics. A live example of ajax filters in WordPress can be found on one of my live projects: https://eubac.org/publications/

WordPress post filters with ajax

WordPress setup for posts and custom post types

Filtering posts, pages, or custom post types with ajax all works in the same way. I assume you have a basic knowledge of javascript and PHP for the syntax and basic WordPress to know how to make a query. You can filter posts and post types by category, tags, and custom taxonomies.

In this example, I will show you how we can filter our custom post type ‘projects’ by category, based on the category slug. There are many ways to query the posts. I’ll give a quick overview of all possible arguments so you can see which ones work best in your project.

Available WP_Query Arguments

WP Query Category Parameters

  • cat: use category ID – int
  • category_name: use category slug – string
  • category__and: use category IDs – array
  • category__in: use category IDs – array
  • category__not_in: use category IDs – array

WP Query Tag Parameters

  • tag: use tag slug – string
  • tag_id: use tag ID – int
  • tag__and: use tag IDs – array
  • tag__in: use tag IDs – array
  • tag__not__in: use tag IDs – array
  • tag_slug__and: use tag slugs – array
  • tag_slug__in: use tag slugs – array

WP Query Author Parameters

  • author: use author ID – int
  • author_name: use user_nicename, NOT name – string
  • author__in: use author IDs – array
  • author__not_in: use author IDs – array

WordPress Page Setup

To get started, we first need a page that queries all our projects and displays them on that page. We also need to query over our categories, to be able to display all available categories. A live example of the filters can be found on the website of my clients – almost any website with a post-archive needs filters, am I right?

So how do we get started with WordPress filters? Let’s display our available categories first:

<?php $categories = get_categories(); ?>
<ul class="cat-list">
  <li><a class="cat-list_item active" href="#!" data-slug="">All projects</a></li>

  <?php foreach($categories as $category) : ?>
    <li>
      <a class="cat-list_item" href="#!" data-slug="<?= $category->slug; ?>">
        <?= $category->name; ?>
      </a>
    </li>
  <?php endforeach; ?>
</ul>

Notice that our first list item is not within the loop. We’ll use this to remove the filters and display all the posts again. In our anchor-tags you see href=”#!”. We need this because I want to use a-tags for the filters, but anchor tags require a href-tag to be a valid HTML element.

Also, note that I am using data-slug in my list-item links. We’ll need this later in our functions to filter on the category slug.

Querying over WordPress Posts and Custom Post Types

<?php 
  $projects = new WP_Query([
    'post_type' => 'projecten',
    'posts_per_page' => -1,
    'order_by' => 'date',
    'order' => 'desc',
  ]);
?>

<?php if($projects->have_posts()): ?>
  <ul class="project-tiles">
    <?php
      while($projects->have_posts()) : $projects->the_post();
        include('_components/project-list-item.php');
      endwhile;
    ?>
  </ul>
  <?php wp_reset_postdata(); ?>
<?php endif; ?>

This is a basic WP_Query to pull in all our posts from post-type ‘projecten’. Make sure our single list items are coming from a separate PHP file. You can use include like I did, or use get_template_part(). Whatever you prefer.

The include file is just a single <li> item, so you can give it whatever markup you want. I won’t include it in this tutorial, because we’re going straight to the point: filtering

Feel free to reach out to me or leave a comment in case you want to see a full setup, that also explains the project-list-item.php file. For testing your code, you can add the_title(); in this file for now.

The WordPress filter with Javascript!

For me as a front-ender, obviously the most fun part! We’ll be running a filter function when we click on a category link we’ve made before. We’ll be doing 2 things:

  1. Make the clicked category the new active filter
  2. Display only the projects that belong to that category

EASY. But let’s not celebrate too early. This is only the JS part, we do need some custom PHP code later on in order to make this stuff work.

$('.cat-list_item').on('click', function() {
  $('.cat-list_item').removeClass('active');
  $(this).addClass('active');

  $.ajax({
    type: 'POST',
    url: '/wp-admin/admin-ajax.php',
    dataType: 'html',
    data: {
      action: 'filter_projects',
      category: $(this).data('slug'),
    },
    success: function(res) {
      $('.project-tiles').html(res);
    }
  })
});

This doesn’t look too complicated, does it? We click our category links and create a new $.ajax call. We will be posting our data to a custom function called ‘filter_projects’. We also need to post the slug we want to filter and we do that by passing our filter to our data ‘category’.

The AJAX URL is the default in WordPress: /wp-admin/admin-ajax.php

NOTE: If the relative URL /wp-admin/admin-ajax.php is not working for you and returns a 404 instead, try using the absolute URL

The PHP WordPress Filter, the horror!

Just kidding. Ajax queries in WordPress with PHP all sounds like a nightmare, but it’s all fun and games! Because we are using the default /wp-admin/admin-ajax.php URL, we can write this function somewhere in the functions.php file:

function filter_projects() {
  $catSlug = $_POST['category'];

  $ajaxposts = new WP_Query([
    'post_type' => 'projecten',
    'posts_per_page' => -1,
    'category_name' => $catSlug,
    'orderby' => 'menu_order', 
    'order' => 'desc',
  ]);
  $response = '';

  if($ajaxposts->have_posts()) {
    while($ajaxposts->have_posts()) : $ajaxposts->the_post();
      $response .= get_template_part('templates/_components/project-list-item');
    endwhile;
  } else {
    $response = 'empty';
  }

  echo $response;
  exit;
}
add_action('wp_ajax_filter_projects', 'filter_projects');
add_action('wp_ajax_nopriv_filter_projects', 'filter_projects');

When hitting the ‘filter_projects’ function, we’ll be checking if we did POST our category-slug. If it’s an empty string, the query won’t filter and it will return all our posts. (we use it when we click the filter “show all projects”)

Next, we run our WP_Query again, but this time with an extra parameter for the category name. Ironically enough, it’s using the SLUG and not the name of the category. Afterward, we just loop over query results and add the template part as a string to our $results.

Don’t forget to register our function, so WordPress knows our ‘filter_projects’ exist. Otherwise, we’ll receive an error when we try to make our ajax call.

Filter Dynamic custom post types with ajax

If you are using multiple post-types that are using the same taxonomies, we can dynamically pass our post_type to our ajax filter function as well. This way we can reuse our previous setup for all posts types on our page.

To be able to do this, we take our PHP list setup from the example above and add a new data attribute ‘data-type’ to also add the post type:

The following code snippets display only the parts that changed from the code examples from before. If something is not working correctly, make sure to use the code from above to set up all the triggers and functions.

<li>
  <a class="cat-list_item" href="#!" data-slug="<?= $category->slug; ?>" data-type="projecten">
    <?= $category->name; ?>
  </a>
</li>

Our data-type can be dynamic as well of course if you’re using the same filter component on all pages. I’ve kept it static in my example for simplicity. Now you can create multiple lists for each post-type and change the data-type attribute to the post type you want to filter.

In our javascript function, we also add an additional parameter in our Ajax filter function:

$.ajax({
  type: 'POST',
  url: '/wp-admin/admin-ajax.php',
  dataType: 'html',
  data: {
    action: 'filter_projects',
    category: $(this).data('slug'),
    type: $(this).data('type'),
  },
  success: function(res) {
    $('.project-tiles').html(res);
  }
});

And at last, we need to make sure our PHP code uses our dynamic post type argument to filter our post types. Update the PHP function code in functions.php to look like this:

function filter_projects() {
  $postType = $_POST['type'];
  $catSlug = $_POST['category'];

  $ajaxposts = new WP_Query([
    'post_type' => $postType,
    'posts_per_page' => -1,
    'category_name' => $catSlug,
    'orderby' => 'menu_order', 
    'order' => 'desc',
  ]);
  $response = '';

  if($ajaxposts->have_posts()) {
    while($ajaxposts->have_posts()) : $ajaxposts->the_post();
      $response .= get_template_part('templates/_components/project-list-item');
    endwhile;
  } else {
    $response = 'empty';
  }

  echo $response;
  exit;
}
add_action('wp_ajax_filter_projects', 'filter_projects');
add_action('wp_ajax_nopriv_filter_projects', 'filter_projects');

Filter custom taxonomies in WordPress with ajax

When filtering your posts using a custom taxonomy instead of the regular tags or categories, we need to edit our query a little bit. The way we can filter on custom taxonomies is slightly different. We need to add a tax_query to our normal query.

a tax_query is an array, existing of arrays. This actually brings us fluently to creating an ajax filter that supports multiple filters at once. But let’s just start with displaying all posts for the selected custom taxonomy.

Our query will look like this:

$projects = new WP_Query([
   'post_type' => 'projecten',
   'posts_per_page' => -1,
   'tax_query' => [
      [
         'taxonomy' => 'product_cat',
         'field'    => 'term_id',
         'terms'    => $termIds, // example of $termIds = [4,5]
         'operator' => 'IN'
      ],
   ]
]);

Note that our taxonomy query is an array of arrays. $termIds is also an array, with all termIds I want to filter on. Thanks to the taxonomy query being its own argument, we can combine taxonomy filters together with other category or tag filters.

Combining multiple WordPress filters with Ajax

combining wordpress ajax filters

Let’s combine multiple filters at the same time! The picture shows example of how it works and will look. It’s somewhat similar to creating the regular filters we did above, but can quickly become a mess. So I’m going to seperate our ajax filters in a seperate filters.js file. The PHP part will still be in functions.php but you can place it in your own WordPress plugin folder if you’d like. I’m not going to cover creating WordPress Plugins, so my solution will be straight in functions.php 🙂

To get started on a filter like above, I created a custom sidebar with all my different filters in it:

<div class="filter--sidebar">
  <input type="hidden" id="filters-category" />
  <input type="hidden" id="filters-creators" />

  <?php 
    // or use include_once('parts/filter-pricerange.php');
    get_template_part('parts/filter','pricerange');

    get_template_part('parts/filter','categories');
  
    get_template_part('parts/filter','creators');
  ?>
</div>

Note the two hidden fields for our categories and creators. We will fill those up with javascript and then post those values to our ajax query. More on this later!

You can add every filter-part you want like this. Because I covered the categories above, I will cover creators first. This is a relationship field with the ACF Plugin.

Our parts/filter-creators.php file shows a list of all our creators/brands. This makes it possible for users to filter on the brand they like. This file simply loops over all our brands and shows them in a list:

<?php 
$get_creators = new WP_Query([
  'post_type' => 'creators',
  'posts_per_page' => -1,
]);

if ($get_creators->have_posts()): ?>
  <h4 class="filter-title"><?= _e('Creators','weichie'); ?></h4>
  <ul class="list-filters">
    <?php while ($get_creators->have_posts()):$get_creators->the_post(); ?>
      
      <li>
        <a href="javascript:;" class="filter-link" data-type="creators" data-id="<?= get_the_ID(); ?>">
          <?= esc_html(get_the_title()); ?>
          <span class="remove"><i class="fas fa-times"></i></span>
        </a>
      </li>

    <?php endwhile; ?>
  </ul>
  <?php wp_reset_postdata(); ?>
<?php endif; ?>

So far so good? I don’t think anything special is going on in here. We create a new WP_Query for our ‘authors’ post type and loop over them. You do need to hide the span. remove the element with CSS. This one we will show only when someone has selected that filter.

Creators ACF Field filter

Maybe it’s also interesting to show you how I’ve linked our creators to the products. I’m using the ACF Pro Plugin to create the fields and place them as a relationship field onto our products. ‘Creators’ is a custom post type on its own.

acf relationship filter

For each product in our store, I can select one or more brands that are ‘related to’ this product. So it would be nice to have if users could now filter our products based on this brand.

Next up: our filter.js file! You don’t have to, but if I like to keep things clear. The filter-file will contain all our js-related filter stuff. So if something breaks or we need to change or add something, it’s always easy to find.

You remember our hidden fields in our PHP filters? Now is the time we’re going to use them. When clicking on a creator, we’ll be putting the IDs of our selected brands in the input field.

filters.js :

$('.filter-link').on('click', function(e) {
    e.preventDefault();
    $(this).toggleClass('activeFilter');

    editFilterInputs($('#filters-' + $(this).data('type')), $(this).data('id'));
    filterProducts();
  });

So for each filter-link we’re clicking on, we’ll toggle our ‘activeFilter’ class (to show/hide the remove icon on the filter), we’ll edit the values in the hidden input fields and we’ll be calling our filterProducts() function to actually filter our products.

To place our values in our hidden fields, we need to make sure we’re not adding a filter twice. Clicking on a filter will add the ID to our hidden field. Clicking on it again will remove the ID from the hidden input field:

function editFilterInputs(inputField, value) {
  const currentFilters = inputField.val().split(',');
  const newFilter = value.toString();

  if (currentFilters.includes(newFilter)) {
    const i = currentFilters.indexOf(newFilter);
    currentFilters.splice(i, 1);
    inputField.val(currentFilters);
  } else {
    inputField.val(inputField.val() + ',' + newFilter);
  }
}

The editFilterInputs function takes an input field – the one where we want to place our filters. Our creator IDs need to go into the creator hidden input field of course. Then we check if the ID already exists in the input field or not. If so, we’ll remove the ID, otherwise we’ll add it.

Our input-field has a value of string. I’m converting the current value in our input field to an array to make it easier to check if the value already exists. If so, remove it, otherwise add it.

Now our main filterProducts() function:

function filterProducts() {
  const catIds = $('#filters-category').val().split(',');
  const creatorIds = $('#filters-creators').val().split(',');
  
  $.ajax({
    type: 'POST',
    url: '/wp-admin/admin-ajax.php',
    dataType: 'json',
    data: {
      action: 'filter_products',
      catIds,
      creatorIds,
    },
    success: function(res) {
      $('#result-count').html(res.total);
      $('#main-product-list').html(res.html);
    },
    error: function(err) {
      console.error(err);
    }
  })
}

The JS part actually looks a lot like a single category/tag/taxonomy filter. But this one will be used for all our filters in the future. In this example, it will work for both creators and categories. You can add all other filters in exactly the same way.

Please note that I am again converting the hidden input fields to an array before posting it to our PHP script to actually make the query and return our response.

Multiple Ajax Filters – PHP part

The PHP part is a bit different than usual. I don’t know if I am explaining any of this clear to you, but please bear with me. We’re almost there and I hope you can make it to work on your project as well!

Getting started in the PHP file, we’ll create our filter_products() filter, receive our $_POST value from the creators and categories and just start with our basic query (without filters) for now.

function filter_products() {
	$catIds = $_POST['catIds'];
	$creatorIds = $_POST['creatorIds'];

	$args = [
		'post_type' => 'product',
		'posts_per_page' => -1,
		'post_status'  => 'publish',
		'orderby'        => 'date',
                'order'          => 'desc',
	];
}
add_action('wp_ajax_filter_products', 'filter_products');
add_action('wp_ajax_nopriv_filter_products', 'filter_products');

Is this still going fine? We get the POST values and create an $args array with our basic query values. We are using a separate $args variable to make it easier to add our multiple filters to the query array.

Next, we’ll add our product categories to the $args array. This one uses the WordPress default ‘product_cat’ taxonomy to filter on a product category: add this part right below our previous $args. In the same filter_projects() function.

// Product Category
if (count($catIds) > 1) {
	$args['tax_query'][] = [
		'taxonomy'      => 'product_cat',
		'field'		=> 'term_id',
		'terms'         => $catIds,
		'operator'      => 'IN'
	];
}

Also, nothing too crazy I think? It takes our $catIds from our $_POST from before. By using $args[‘tax_query’][] we add this array to the already existing $args array from before. We’re not overwriting anything, we’re adding a ‘tax_query’ array to our previous array.

We’ll be adding our creators’ filter to the array the same way. Only is Creators a Custom Post Type and not a Taxonomy. So instead of using tax_query we need to use meta_query for this field:

// Product creator - Every creator needs a seperate meta_query
if(count($creatorIds) > 1) {
	$args['meta_query']['relation'] = 'OR';

	foreach($creatorIds as $creator) :
		if ($creator != '') :
			$args['meta_query'][] = [
				'key' => 'product_creator',
				'value' => '"' . $creator . '"',
				'compare' => 'LIKE',
			];
		endif;
	endforeach;
}

Each creator needs its own [‘meta_query’] array. Only if there is a creator selected, we’ll be adding this filter to our $args.

Now our $args filter has the default query for our products, a tax_query for our categories, and a meta_query for our ACF Relationship queries.

Now it’s time for the main query. This one is exactly the same as the singular filters from above, but I changed the response a little bit so we receive our query total (to see how many products are in the filter) and then our $html result.

Place this code in the same filter_products() function, right below our previous $args functions for categories and custom post types:

if ( $ajaxproducts->have_posts() ) {
	ob_start();
	while ( $ajaxproducts->have_posts() ) : $ajaxproducts->the_post();
		$response .= wc_get_template_part( 'content', 'product-dibbz' );
	endwhile;
	$output = ob_get_contents();
	ob_end_clean();
} else {
	echo __( 'No products found' );
}
	
$result = [
	'total' => $counter,
	'html' => $output,
];

echo json_encode($result);
wp_reset_postdata();
exit;

WordPress Ajax Filter Summary

This is all we need to make ajax filters work in WordPress. Let me know if the ajax filters were easier than you thought or if you’re still having difficulties.

A more advanced, dynamic ajax filter for Woocommerce will follow somewhere next month. I still have some difficulties with filtering e-commerce products with ajax to make it super fast. The post will be ready when I’m done struggling. 😉

Edit 2021: I get a lot of reactions about doing a tutorial with ajax filters on default WordPress posts (the WordPress index page and category.php) – but then with Ajax. I want to write a tutorial on that topic as well, but it actually works the same way as this article. Except that WordPress handles the first post-query for us by default.

Please let me know if this tutorial escalated too quickly and you would prefer a more basic guide using the WordPress archive pages with ajax post filters.

Leave a Reply

Your email address will not be published. Required fields are marked *

154 Comments

  • Mike says:

    Hi, is it possible to make same filter but with woocommerce products? If it is, could u give me some hints?

    • weichie says:

      Hi Mike, starting from the subtitle “Combining multiple WordPress filters with Ajax”, this article handles product filtering using ajax. Currently, it filters on custom ACF fields and categories, but the same method will also work for product attributes. You can query those with prefixing ‘pa_{attribute-name}’. Hope it helps you in the right direction!

  • AI Matt says:

    <a class="cat-list_item" href="#!" data-slug="slug; ?>” data-type=”ai_tools”>
    name; ?> but the url is _/#!

    how do i show category name instead – this is for SEO

    • weichie says:

      Hi Matt, indeed the #! gets crawled for SEO. You can replace the a-tag for a button-tag if you don’t have a link, or replace the “#!” with “javascript:;” to prevent #! being added to the sitemap.

  • Geert says:

    Hi Bob,

    Nice post. I think this might contain the solution to what I am trying to achieve making a wordpress post for a book on which I query content.

    At the moment I have chapters setup als a custom taxonomy and every paragraph is a custom post entry. The filter which I got working using Jquery changes the posts category itself (wp set terms), but that’s a backend change.

    However, with the snippets above I have some difficulties translating them to my situation, specially the PHP filter. I am using Generate Blocks plugin and would like to keep that, but build this filter on top of it.

    Can you help me out here? It’s ok that it would be a paid excercition.

    Best,
    Geert

  • Mark says:

    How to add both Ajax filter and Ajax load post button?

  • Steven says:

    Hi Bob,

    Thank you for your guidance in this article. I’d luv to get my copy working. I’m using the default wordpress posts and categories taxonomy. My issue: when I click a category, I get a “There has been a critical error on this website”. My debug.log has “PHP Fatal error: Uncaught Error: Call to a member function is_block_editor()…wp-includes/script-loader.php:2500”.

    I see the JS updating the category links but my list doesn’t update. I’ve quadruple checked all my markup, JS, and PHP. I’ve used dev tools to inspect a couple of the example sites you have linked above. I’ve googled the errors I’m getting and nothing seems helpful. Any ideas what’s the real issue?

  • Muhammad Uneeb says:

    hi can i request a tutorial for ajax filter for custom (without plugin) metaboxes on custom post type as dropdown menus plsss if u can help

  • Chris says:

    Hello,
    You have done an amazing job with this tutorial!!
    I have three post caterogies that i get from the get categories as follow:
    ‘name’,
    ‘order’ => ‘ASC’,
    ‘include’ => ‘2,3,6’
    ) );
    ?>

    <a class="cat-list_item" href="#!" title="filter categories" data-slug="slug; ?>”>
    name; ?>

    So my question is can i make default the category with id 2 so it shows its posts and have the class active?
    Thank you in advance for your helf!! Much appriciated!!

    • Chris says:

      Hello,
      I am trying to make a project with this filter and i would really appreciate any help!
      I have filter for post categories and this filter works like a charm but it doesn’t have this final detail to be perfect. Please someone help!!

      Thank you and i am so sorry for the persistence

      • Bob Weichler says:

        Hi Chris, the code above is incomplete as you cannot comment code snippets for security reasons. Please use something like pastebin to share your code. But having a default filter active is something you need to do without ajax. You run the default query on page-load for only the post category that you want (hardcoded query), and add the active class to the filter where post_id = 2.

  • Muhammad Uneeb says:

    Hi, I am facing a problem regarding the ajax filter, im working on a localhost and my website console returns error on filter.js which says “$ is not defined”

    • Bob Weichler says:

      Hi Muhammad, Thank you for your message. If $ is not defined, the jQuery library is not loaded correctly on the page.

      • Muhammad Uneeb says:

        can i have the source code to this code. I’m a beginner so I wanna figure things out but this tutorial is missing out a lot of things that I cant understand so an example of the code working out properly would be a huge help. thank you so much

  • Andry says:

    Hi, I would like to ask how to work with this filter, if it is necessary to display not all filtered posts at once, but with pagination? `’posts_per_page’ => ‘5’,` ?
    I write somthing like:

    ** code removed **

    I see pagination buttons, but when I go to `2` link I got Page with ‘0’

    • Bob Weichler says:

      Hi Andry,

      Please use an external website, like pastebin or so, to paste a bunch of code. I only allow small code-snippets here. I do have a blog about ajax pagination as well, that you can find here: Load more posts with ajax in wordpress, without page reload.

      You can combine the ajax filters from this post, together with the ajax load more posts from the other article.

      Hope it helps!

  • Sheree walker says:

    Sorry – I realise now my first comment was in moderation but just a further development.

    If I remove the href=”#!” it actually works but the ‘all’ category does not display by default, it is blank until you click a filter.

    Thanks again for any help!

    • Bob Weichler says:

      Hi Sheree, thank you for your comments. You still need to run your default query on page load, otherwise, there are no posts loaded to the page. This filtering is indeed only triggered after clicking on a specific filter.

      I would advise running your first query in PHP when you come on the page and using the ajax options only for the filtering.

  • Sheree walker says:

    Hi there, I’m not sure my previous comment worked. Apologies if I have posted this twice.

    Thank you very much for this article. Unfortunately I can’t get it to work and I’m not sure why. I keep getting the console error unrecognised expression: #!

    I have fiddled about with the code but nothing works. Any help would be greatly appreciated

    Thanks!

  • Sheree walker says:

    Hi there,

    I’m not sure what I am doing wrong but i can’t seem to get this to work.

    I get the console error
    jquery.min.js?ver=3.6.0:2 Uncaught Error: Syntax error, unrecognized expression: #!

    I have played about with the code but nothing seems to work

    Note on the news page – there ARE posts there, but they are just there from before we decided to add post filters. They will be removed and replaced if I can get this to work.

    Currently my post template is just the post name just to see if i can get it working.

    Any help would be greatly appreciated

    Thanks

  • Alfina says:

    Hello, I am using pro builder and your code doesnt work with that, I have added the js file in the builder and I am using archive page for search products. Can you help me what I can do to work this code?

    • Bob Weichler says:

      Hi Alfina,

      Thank you for your message. As you mentioned and as the title of this article states: this is a custom solution without plugins or page-builders. You should be able to use the same logic in your project, but I cannot assist you with that. Apologies.

  • Joel says:

    Incredible, so clear and accurate and just what i needed thank you so much.

  • Sam says:

    Thank for this, I only have one question. I am trying to filter custom taxonomies so i went to that section but the code uses “‘terms’ => $termIds, // example of $termIds = [4,5]”? Where has $termsIds come from as I cannot see any mention of it before?

  • Chris says:

    Hi Bob,
    I’ve managed to apply your example to a project I’m working on, however I’m having an issues. I have two custom Taxonomies (Location & Status). In my code I have two tax_query and the relation set to AND. When I filter by either Location or Status it works, however If I pick a Location and a Status then it brings back all of the results rater than filtering by both Taxonomies. I logically thought it would be that it would only show the result that match both Taxonomies options.

    Here is a sample of my code.

    $args = [
    'post_type' => 'asset',
    'posts_per_page' => -1,
    'post_status' => 'publish',
    'orderby' => 'post__in',
    'order' => 'desc',
    ];

    $args['tax_query']['relation'] = 'AND';
    // Centre Category
    if (count($centreIds) > 1) {
    $args['tax_query'][] = [
    'taxonomy' => 'centre',
    'field' => 'term_id',
    'terms' => $centreIds,
    ];
    }
    // Status Category
    if (count($statusIds) > 1) {
    $args['tax_query'][] = [
    'taxonomy' => 'asset_status',
    'field' => 'term_id',
    'terms' => $statusIds,
    ];
    }

    Thanks

    Chris

    • Chris says:

      Hi Bob,

      I managed to get my issue resolve. However I’m now having a new issues. Currently the ajax only seems to run when you are logged in as an administrator. Why I log in as an editor or Author the ajax doesn’t seem to load. Have you ever experienced this issue and know if there is a fix?

  • Mo says:

    Hi! Great article and blog. Did you ever get to finalizing the woocommerce Ajax filter? Would really love to see. Thanks!

  • Alfredo says:

    great article! many thanks

  • Amy says:

    I’d like to say that I’m not very good with Javascript or jQuery so would appreciate some help!

    I changed the script so that the “All” button would disappear once clicked on but how would I go about refactoring this code?

    $('.credit-category-list_item').on('click', function() {
      $('.reset').removeClass('d-none');
      $('.credit-category-list_item').removeClass('active');
      $(this).addClass('active');
    
      $.ajax({
        type: 'POST',
        url: '/wp-admin/admin-ajax.php',
        dataType: 'html',
        data: {
          action: 'filter_articles',
          category: $(this).data('slug'),
        },
        success: function(res) {
          $('.article-tiles').html(res);
        }
      })
    });
    
    $('.reset .credit-category-list_item').on('click', function() {
      $('.credit-category-list_item').removeClass('active');
      $(this).parent().addClass('d-none');
    
      $.ajax({
        type: 'POST',
        url: '/wp-admin/admin-ajax.php',
        dataType: 'html',
        data: {
          action: 'filter_articles',
          category: $(this).data('slug'),
        },
        success: function(res) {
          $('.article-tiles').html(res);
        }
      })
    });
    • Bob Weichler says:

      Hi Amy, could you send me a screenshot or an example of your HTML setup as well please? Right now I cannot visualise how you’ve setup your reset button as the .reset is a parent wrapper for the .credit-category-list_item and I would like to know why.

      • Amy says:

        Hi Bob-
        Sorry, I didn’t get a notification for a response so this is a late reply.

        So right now, the homepage displays all posts except sticky because that is in a carousel above.

        Once they filter by category, the carousel will go away and all posts including sticky will display.

        I also run into another thing where I don’t know to NOT display the sticky post once the user hits All.

        All

        <a class="credit-category-list_item" href="#!" data-slug="slug; ?>”>
        name; ?>

        get_option( ‘sticky_posts’ ),
        ‘post_type’ => ‘post’,
        ‘posts_per_page’ => -1
        ]);
        ?>
        have_posts()): ?>

        have_posts()) : $articles->the_post();
        get_template_part( ‘template-parts/card/article-list-item’ );
        endwhile; ?>

      • Amy says:

        Category Filter – https://codeshare.io/eVe39l
        Article Listing – https://codeshare.io/JbYzbr

        Here are the 2 links, I figured it be easier than pasting the code haha.

        Currently, on the homepage, it lists all the posts excluding sticky posts because it is in a carousel above the listing. Once the user filters by category, the sticky posts will also appear. However, I also need to know how to write the code so that once the user hits All, it resets and excludes the sticky posts. At the moment when I hit All, it displays everything.

  • Amy says:

    I just wanted to say thank you so much for this. It has really helped me since I’ve never filter posts with ajax before. Much appreciated!!

  • Osmana says:

    Hi,
    Thank you for the great post.
    When I click a link it retuns the same all posts from my custom post type. Also I have tried with normal post nad it gives the same result.

    Here is my code;
    filter-template.php – https://codeshare.io/bvkEBw
    functions.php – https://codeshare.io/xvEXBX
    templates/tour/loop-list.php – looping all posts from tour_type taxonomy

    I’d appreciate if you could help me fix it.

    • Bob Weichler says:

      Hi Osmana,

      Thank you for your message. In your functions.php function, you’re storing everything in $response as in my example. But in the end, you do `echo $catSlug;` You need to echo your $response. You’re also adding 2 things in the same variable, the catSlug and the template_part. This is mixed content you need to avoid.

      //Your functions.php
      ...
      if($ajaxposts->have_posts()) {
        while($ajaxposts->have_posts()) : $ajaxposts->the_post();
          $response .= $catSlug; // <- remove this line or store in different variable
          $response .= include(get_template_directory() . '/templates/tour/loop-list.php');
        endwhile;
      } else {
        $response = 'empty';
      }
      
      echo $response; // not echo $catSlug;
      exit;
      
  • Daniel says:

    Hi there,

    I’m struggling to get this to work with a meta query using ACF radio values. Is there any chance you could point me in the right direction please? Here’s my code:

    function filter_projects() {
      $catSlug = $_POST['category'];
      
      $ajaxposts = new WP_Query([
        'post_type' => 'portfolio',
        'posts_per_page' => -1,
        'meta_key'      => 'status',
        'meta_query'    => array(
          array(
    	'key'     => 'status',
    	'value'   => array('active','seeking','closed'),
    	'compare' => 'IN'
          )
        ),
        'orderby' => 'date', 
        'order' => 'ASC',
      ]);
    
      $response = '';
      
      if($ajaxposts->have_posts()) {
        while($ajaxposts->have_posts()) : $ajaxposts->the_post();
          $response .= include get_theme_file_path('/portal/inc/project-list-item.php');
        endwhile;
      } else {
        $response = 'empty';
      }
      
      echo $response;
      exit;
    }
    
    add_action('wp_ajax_filter_projects', 'filter_projects');
    add_action('wp_ajax_nopriv_filter_projects', 'filter_projects');
    

    I think the problem is in this following code, but I’m not sure where:

    $('.cat-list_item').on('click', function() {
      $('.cat-list_item').removeClass('active');
      $(this).addClass('active');
    
      $.ajax({
          type: 'POST',
          url: 'https://investor-forum.local/wp-admin/admin-ajax.php',
          dataType: 'html',
          data: {
            action: 'filter_projects',
            category: $(this).data('slug'),
          },
          success: function(res) {
            $('.project-tiles').html(res);
          }
      })
    });
    
    • Bob Weichler says:

      Hi Daniel,

      Thank you for your comment. Do you have any error logs in the console or your network? The way how I mostly test this is to first create the PHP query in the page to see if that works. If that works, I move it to the functions.php file and add the JS. Then it’s important to keep an eye on your chrome console to see the JS errors and the network tab in dev-tools to see what you’re sending to your PHP query and what you’re receiving.

      From this example, I can’t really see what’s going wrong. If you have an error message in the console or the network I can have another look.

      I don’t think you need meta_key AND meta_query in the same query. Like this:

      new WP_Query([
        'post_type' => 'portfolio',
        'posts_per_page' => -1,
        // 'meta_key'      => 'status', <= Remove this line
        'meta_query'    => array(
          array(
            'key'     => 'status', // <= You have the meta_key here
            'value'   => array('active','seeking','closed'),
            'compare' => 'IN'
          )
        ),
        'orderby' => 'date', 
        'order' => 'ASC',
      ]);

      Hope it helps!
      Bob

  • Clément Couillet says:

    Hello, We have a problem with the filter. We have two selects (One for posts category and one for type category). We followed the tutorial but when we choose an option, the landing page appeared instead of posts. Can you help me please ?

    We have this error in the console : Uncaught Error:
    Automatic publicPath is not supported in this browser
    at :2:3684
    at :2:3844
    at :2:4919
    at b (jquery.min.js?ver=3.6.0:2:866)
    at Function.globalEval (jquery.min.js?ver=3.6.0:2:2905)
    at Object.dataFilter (jquery.min.js?ver=3.6.0:2:80720)
    at jquery.min.js?ver=3.6.0:2:79185
    at l (jquery.min.js?ver=3.6.0:2:79587)
    at XMLHttpRequest. (jquery.min.js?ver=3.6.0:2:82355)
    at Object.send (jquery.min.js?ver=3.6.0:2:82714)
    public.css:1 Failed to load resource: the server responded with a status of 404 (Not Found)
    public.css:1 Failed to load resource: the server responded with a status of 404 (Not Found)

    • Bob Weichler says:

      Hi Clément,
      Thank you for your comment. Unfortunately, this error is unrelated to the content of this post. Something is conflicting with your dev setup but I cannot assist with that. If a code snippet in my example is the cause of this error, you’ll need to rewrite it in a way your bundler can handle it.
      I hope you find a solution!

  • Alecz Eighmard Deyto says:

    Learned a lot of things with this, thank you! By any chance, do you also have a tutorial on how to integrate search on blog inside the code. Thank you

  • Ruddi Aagaard says:

    Hi,

    where do i put the PHP code that not going to functions.php?

  • Ruddi Aagaard says:

    Thank you for the great post.
    It gets quite complicated for me, any chance of a github repo of the full working code?

  • Roman says:

    Hey, Bob!

    I’ve made some changes to deal with a custom taxonomy ‘project-type’ instead of generic category. Seems to be working, but I’m not good at taxonomies yet, so ‘All projects’ link shows ’empty’. I’d appreciate if you could help me fix it.

    https://pastebin.com/8DWDRhkQ

    Have a good day!

    • Bob Weichler says:

      Hi Roman,
      Thank you for your message. Only the ‘All projects’ filter is not working? If you include the tax_query in your $args, you can’t leave it empty. I usually wrap it inside an if-statement and only add the tax_query if we are filtering on taxonomies. This will remove the tax_query when we’re filtering for all projects.

      $catSlug = $_POST['category'];
       
      $args = [
        'post_type' => 'project',
      ];
      
      if($catSlug) {
        $args['tax_query'][] = [
          [
            'taxonomy' => 'project-type',
            'field'    => 'slug',
            'terms'    => $catSlug,
          ],
        ],
      }
      
      $ajaxposts = new WP_Query($args);
      

      Hope it helps!

  • Tim says:

    Dude, this is great! Thank you so much for this.

  • Monica says:

    Is possible to have the project-list-item page template ?

    • Bob Weichler says:

      This template can be anything you want. It’s the li element that goes inside of your ul. My code highlighter doesn’t let me inject html-tags, so here’s a screenshot of my template file: – image removed –

    • Michael says:

      Hi Bob,
      one more thing – maybe there is a solution or at least hints somewhere here anyway and i just didn’t see it. Is there a simple way to somehow highlight (make bold or italic or larger or change color or repeat in a tag below the ) the category, the user clicked on, so that there is always visible, which selection is on display on the page at the moment?
      all best
      Michael

      • Bob Weichler says:

        Hi Michael, do you mean an active state on the filter? You can add an active class when clicking on the button

        $('.cat-list_item').on('click', function() {
          $('.cat-list_item').removeClass('active');
          $(this).addClass('active');
          ...
        }
        
      • Michael says:

        Hi,
        to achieve this, just style the css-class “.active” which is used in the js-file to do the selection. Very simple and easy. I answered myself, when i realized, that I simply should have had a closer look at my code to see how easy that is!
        M

  • Tushar Laad says:

    Would appreciate a more basic version with all filters, and search bar for normal posts in specific categories

  • Michael says:

    Hi Bob,
    thank you very much, worked quick and easy with a custom post-type and categories (also excluding some of the not needed categories), there is just one strange thing, maybe you have a hint on this: the items in my displayed list of filtered posts have a “more” Button to display extra-content, which works with jquery. After implementing the filtering options this works when i load the page, but after filtering the posts once the functionality of that button is gone and i have no idea why that is so (except that it probably has to do with my js-file which contains all js+jquery code, i added to the site).
    all best
    Mike

    • Bob Weichler says:

      Hi Michael,

      Thank you for your comment. As a first thought, this is because a click event is registered on all the elements at page-load, but does not work for dynamically added content afterwards. To solve this issue, you should rewrite your click trigger from the element, to a parent element. We can then check the clicked target and only trigger your click function when the target is the “more” button.

      Here’s an example of what you can do:

      // Old code? - Wild guess of course
      $(".more").on('click', function() {
        // Your click function
      });
      
      // Update to work with dynamic content
      $(document).on('click', '.more', function() {
        // Your click function
      }
      

      Hope this does the trick for you!

      • Michael says:

        Hi Bob,
        a thousand thanks! Your guess was good, probably I would have found out what to do, too – after an hour of trial and error or so. This did it in seconds!
        Mike

    • Bob Weichler says:

      Hi Upendra,

      Thank you for your feedback. I am currently writing a new tutorial on loading more posts through ajax that will cover the pagination with ajax as well. It’s not focussing on filtering, but the ajax part to pull posts will be the same. Instead of getting posts per category, I’m getting posts from page 2.

      I’ll hope to get it up later this week!

  • upendra says:

    Hi,
    Add Pagination for more useful.

  • Eleonora says:

    Hi,
    I just wanted to thank you for the excellent explanation.
    You made my day

  • Meir kalmus says:

    Hi
    Do you think your code could improve the filter time executions in this site?
    https://meirtv.com/find-shiurim/

  • Yointic Marketing says:

    Thank you for sharing this excellent post. It is very technical and detailed information, we will try to apply what is published in the article.

  • Tim Vink says:

    Hey Bob,

    I can’t seem to get this thing working. Console is outputting no errors but nothing happens when i click a category.

    I’m not using this with custom post types btw, just default wordpress posts.

    Here’s a pastebin: https://pastebin.com/RbWLSixr

    And here’s my page i’m testing on: https://larrysvisuals.nl/projects

    Could you take a look at it?

  • Annemarie says:

    Thank you so much! This blog / tutorial was a great help for creating my own filter in a new project. I learned a lot the past two weeks, struggling between all code lines. But I fixed it.

  • Shiny says:

    Hi Bob!
    Thank you so much for this tutorial, I need to filter different post types on different pages, so instead of writing the multiple functions for multiple post types, I decided to create a shortcode function and use it, but when I try to pass multiple taxonomies into the shortcode attribute it isn’t passing the different taxonomy types and its values, below are my codes

    filter.js – https://pastebin.com/4drNn2b3
    function code – https://pastebin.com/wBR5CGB8

    currently it is only passing one taxonomy only

  • Marcelo says:

    Hi, Bob!

    Do you have an example of this with vanilla JS? I’m trying to keep my site jQuery free. I tried turning it to vanilla but can’t seem to make it work.

    Thank you!

    • Bob Weichler says:

      Hi Marcelo,

      Thank you for your message. I do not have an example for this in vanilla JS as this is made for WordPress filters, and WordPress comes shipped with jQuery by default. But if you would like ES6 solutions for API calls in JS, you can try using the Fetch method. Or use Axios, but that’s another library I assume you don’t want to use if you’d like your site jQuery free.

      Cheers,
      Bob

      • Marcelo says:

        Thank you for the reply, Bob. The thing is that I never use WP scripts since I very rarely use them, I try to keep everything lean. I’ll continue to try things out.

        Thanks!

  • Nito says:

    Hey i want to do search filter for woocommerce shop

  • Nito says:

    How can i do this with woocommerce products. Please i need your help. In i’ve tried several plugins on https://loneto.org/ but none is good enough even premium ones. I want the products to display as shown in http://www.mrbooks.com or as they’re shown in https://loneto.org/product-category/administracion/

  • mich says:

    Hi Bob,

    Thanks for the tut!

    The multiple filters is not working for me. The first part, categories filter works without multiple filters using radio buttons but when combined with custom post types, it doesn’t work.

    What I’m trying to do is have 3 filters.
    1. Filter by custom post type include default WP post type. (Filter by ‘post’, ‘cpt1’, ‘cpt2’)
    2. Filter by Categories (cat1, cat2, cat3)
    3. Filter by a specific taxonomy named ‘tax1’ (this taxonomy is under normal WP posts)

    With the combination of these filters, I should be able to display posts related to the filter chosen. Any thoughts on how I could combine your logic to work with my situation? Thanks a lot in advance.

  • Shaha says:

    Hey Bob,
    Thanks for the tutorial, but unfortunately the multiple combinations filter doesn’t work at my end, however single category filter is working, but when I try to combine filters with your above tutorial, it doesn’t work, and gives an error in console like this https://ibb.co/ZGyzFt3, I need to filter posts based on category, tag and popularity/asc/dsc/date etc like this https://prnt.sc/1ze3rr4, also when I click on filter links it doesn’t show any result, could you please look into this, and assist me where am I doing wrong, would highly appreciate a quick response towards this, thanks for the kind response.
    I have pasted my code below :

    added in function.php : https://pastebin.com/tYkkM7p0
    js code : https://pastebin.com/NNwuiVr4
    portfolio template : https://pastebin.com/9HkULXWi

    • Bob Weichler says:

      Hi Shana,
      Thank you for your response. What I can see in the error-message is that the code breaks on your declaration of the current filters:

      const currentFilters = inputField.val().split(',');

      I think because your JS breaks here, it doesn't continue with the rest of your script. If you look at the network tab in your browser with the dev-tools open, do you see your admin_ajax script being called or not? To fix your JS error, you can only run the .split() function on an array. So I would wrap it in an if-statement and see if the code continues after that

  • Rohit says:

    i have only one question, where i put or write this code ,i mean on which file can you explain, thanks 🙂

    • Bob Weichler says:

      Hi Rohit,
      Thank you for your reply! You should be able to write all of the JS code in your external js file. The WP query part where we write the ajax function needs to go in the WordPress functions.php file. The other snippets, like displaying your categories etc, should go in your template files where you want to display your filters.

  • Alan says:

    Thank you for the great post.
    It gets quite complicated for me, any chance of a github repo of the full working code?

    • Bob Weichler says:

      Hi Alan,
      Thank you for letting me know! I have a new filter-post on product attributes for WooCommerce planned for publication soon. I will then also add a github repo for a full demo.

  • Francesco says:

    Hello.
    Thanks you helped me so much at work.
    I have only one problem. Categories aren’t showing quickly? I want them to be loaded right away. please can you help me figure out how to solve?

    I apologize for my very bad English

  • Sam says:

    Hello first of all thank you for this very well done tutorial, which was very useful for me not having to install huge plugins.
    I’m having a problem and I don’t know where the mistake came from. Every time I click on a link the ajax query runs with the right information in the form but the result is always empty.
    Here is the pastebin:
    https://pastebin. com/rF4QeN0X
    And the query
    https://pastebin. com/jQqLYQak

    Can you help me, please?
    Good to you.

    • Bob Weichler says:

      Hi Sam,

      Thank you for your comment. I had a quick look and I see that you’re filtering in your $ajaxposts on ‘category_name’ and on ‘product_cat’ inside your tax_query. You’re using $catSlug for both of them, while category_name expects a string, and ‘product_cat’ expects an ID. So my first guess would be to debug deeper on that part of your code.

  • Bohdan Batsenko says:

    Hi.
    The filtering won’t work.
    As soon as I change ajax url from relative to absolute in JS code:
    url: “http://localhost:3000/testwebsite/wp-admin/admin-ajax.php,
    filtering works perfectly.
    Windows + XAMPP dev environment.
    Why so?

    • Bob Weichler says:

      Hi Bohdan,

      Thank you for your comment. The filtering works fine but this is a server configuration setting that makes it so you can use relative URLs or not. Also, in your absolute URL, I see that you’re using subfolders for your projects. This is perfectly fine, but then your URL should be /testwebsite/wp-admin/admin-ajax.php – if your environment supports relative URLs.

      I will update it in my post that if the filtering returns a 404, you can try the absolute URL.

  • Jessica says:

    Hi!
    This is great I have been looking around a lot for this type of function for a long time.
    I have a problem, not sure if it is something you could help with but I get an “500 (Internal Server Error)” when I’m trying to click the filters, the active class is added so thinking the problem comes after that.

    https://pastebin.com/SCDexXut

    Thanks for a great article!

    • Bob Weichler says:

      Hi Jessica,
      Thank you for your response and pastebin share. I think that you’re mixing 2 of my examples into your code. You create your WP_Query in a variable $projects, but loop over $ajaxposts. I think this should fix the 500 error:

      $projects = new WP_Query([...]);
      if($ajaxposts->have_posts()) { ... }
      
      // Change to:
      $ajaxposts = new WP_Query([...]);
      if($ajaxposts->have_posts()) { ... }

      Hope it fixes your issue!
      Best,
      Bob

      • Jessica says:

        Hi Bob!
        That solved the 500 problem, thank you!
        However, it still doesn’t filter my posts. I have changed the class from “project-tiles” to “single-books-grid” which is the class I’m using, but when I click the filters it returns “empty”. If I add terms id to my function it returns “111”.

  • SP says:

    Hi

    I need to add a filter by user/author.
    it need to be searched from an ajax loaded search and from the selected author, it needs to filter the posts.
    As of now, the catagory filter is working fine as expected.

    • Bob Weichler says:

      Hi there,

      Thank you very much for your comment. If you go the the WP_Query documentation you can see what parameters are available to query on author as well: https://developer.wordpress.org/reference/classes/wp_query/

      The available author parameters for the WordPress query are:

      author (int) – use author id.
      author_name (string) – use ‘user_nicename‘ – NOT name.
      author__in (array) – use author id (available since version 3.7).
      author__not_in (array) – use author id (available since version 3.7).
  • Guado says:

    Great Tutorial, thank you so much!

    I was wondering about exposing in the ajax call the wp-admin url: ‘/wp-admin/admin-ajax.php’.
    Do you think that a simple plugin like WPS Hide Login which changes the default wp-admin url could interfere with admin.ajax.php ?

    Hope to see soon the dynamic ajax filter for Woocommerce aswell 🙂

    • Bob Weichler says:

      Hi Guado,

      I don’t think that this would be a huge security issue. But there is a plugin called “Hide My WP” that gives you the option to also hide the admin-ajax URL. Or if you prefer it without plugin (like me), you can also add the following function to your functions.php:

      function modify_adminy_url_for_ajax( $url, $path, $blog_id ) {
          if ( 'admin-ajax.php' == $path ) {
              $url = site_url('/api/');
          }
          return $url;
      }
      add_filter( 'admin_url', 'modify_adminy_url_for_ajax', 10, 3 );

      Once that is added we need to let WordPress know that we modified our request URL so that it’s not listening to admin-ajax anymore. This can be done by adding the following line to your htaccess file:

      RewriteRule ^/?api/?$ /wp-admin/admin-ajax.php?&%{QUERY_STRING} [L,QSA]

      From then on, all your AJAX requests should be visible as /api/ and no longer through wp-admin/admin-ajax.php

  • Elin says:

    Hi,

    thank you so much for this. I’ve been struggling with this for a while now, trying to use no-good plugins but now I decided to really just create my own filter. This tutorial has been a great help, I’m well on my way, but something’s not right. Here is my code:

    https://codeshare.io/dwAEmz

    The Ajax function is in my functions.php. I’ve managed to call the categories and display them on the site as well as the template part for each post within the category, but my links don’t work. Nothing happens when I click the links except I get ‘#!’ at the end of the url.

    Can you please help?

    • Bob Weichler says:

      Hi Elin,
      I’m glad you’re trying custom filters instead of using bloated plugins. Does your console returns any errors when clicking the link, or the page simply refreshes with #! in the URL? Could you try adding a preventDefault() in your click function? Something like this:

      $('.cat-list_item').on('click', function(e) {
        e.preventDefault();
        console.log('clicked');
        $('.cat-list_item').removeClass('active');
        $(this).addClass('active');
      ...
  • Cladif says:

    Thanks for this valuable information! This looks great !! 😉
    How can I get a “dynamic count” of the categories?

  • Prince Allen says:

    Hi Bob Weichler,
    It is nice blog but there is something wrong but i don’t know. Can you please make a video tutorial on it for custom post type.
    Thank You

  • Nilesh Jaiswal says:

    I am trying this code on XAMPP localhost but it is not working. Does it work only on the server?

    • Bob Weichler says:

      It should work everywhere, not only on the server or xampp. Something else in your code/setup might still go wrong then.

  • Brad says:

    i have tried multiple combinations and all return empty even attempted pasing term ids into the array but same result.

    $ajaxposts = new WP_Query([
    ‘post_type’ => ‘faqs’,
    ‘posts_per_page’ => -1,
    ‘orderby’ => ‘menu_order’,
    ‘order’ => ‘desc’,
    ‘tax_query’ => [
    [
    ‘taxonomy’ => ‘faq_cat’,
    ‘field’ => ‘term_id’,
    ‘terms’ => array( 787, 1662, 786, 1229),
    ‘operator’ => ‘IN’,

    ],
    ]
    ]);

    Ido appreciate your replies but in my case, your code does not work, which is a shame because this is exactly the solution i require.

    • Bob Weichler says:

      Hi Brad, if the query keeps returning empty results I’m afraid something else in your setup is going wrong. You can try debugging using Postman. But through the comment section on our site, this is all the help I can offer you.

    • Jet says:

      Why not just send him the task you need doing?

  • Brad says:

    This is exactly what im looking for and really appreciate you writing this but for some reason im coming up “empty” every time i do the ajax call

    • Bob Weichler says:

      Hi Brad,
      Thank you for your comment. Do you have a Pastebin or something of your code? I’m guessing either your post/taxonomy type is wrong or something is going wrong in the combination of filters.

      • Brad says:

        https://pastebin.com/JgpEUtEg

        my post type is faqs and if i print_r after $response = ”;

        Heres my result https://pastebin.com/JnneQCdH

        • Bob Weichler says:

          Hi Brand,
          Thank you for the additional info. I see that you’re using get_terms() for your categories, which is correct if you’re using custom taxonomies. In that case, you can’t use ‘category_name’ in your query but you need to use the ‘tax_query’ option and pass ‘faq_cat’ for taxonomy to make this work. Or use get_categories() if your FAQs are using the default categories from posts.
          Hope it helps!

          • Brad says:

            I truly appreciate your help and this but
            $ajaxposts = new WP_Query([
            ‘post_type’ => ‘faqs’,
            ‘posts_per_page’ => -1,
            ‘orderby’ => ‘menu_order’,
            ‘order’ => ‘desc’,
            ‘tax_query’ => [
            [
            ‘taxonomy’ => ‘faq_cat’,
            ],
            ]
            ]);
            still comes up empty.

          • Bob Weichler says:

            Hi Brad, you should pass all parameters in your tax_query and not only the ‘taxonomy’=>’faq_cat’. You also need the field, term and operator in that array.

          • Brad says:

            Hi Bob still having no luck with this i have added passed the tax query and get empty if i pass the term ids as array it i get a result but i get all results not just the ones from the selected category
            Query and script
            https://pastebin.com/nAsJT8cj
            Functions and cpt/tax details
            https://pastebin.com/7gXuBnVg

            Could you please look and see what is missing

          • Bob Weichler says:

            Hi Brad,

            Thank you for your message but I’m not quite sure if I’m following what you’re trying to do. You hardcoded $termIds = array(‘1821′,’1820’); in both your initial query as your ajax query. But you are not catching any $_POST[”] in your ajax query at all. So no matter what you select, the query will always return the same.

          • Brad says:

            if i don’t hard code the term ids it returns empty so i don’t understand what step i have missed

          • Bob Weichler says:

            Hi Brad,
            You may only pass the tax_query if you actually have terms. If the array is empty, you need to remove it from your query or it will crash/return empty. Check the section in my blog with the “Multiple Ajax Filters – PHP part” again. There I first create the $args and check if I have $catIds or not. Only if I have $catIds I will add the tax_query to the WordPress query. But if $catIds is empty, I need to remove the whole tax_query from the $args.

            Hope it’s a bit clear?

            Cheers,
            Bob

  • Julien says:

    Hello, thx for the tuto. It’s work very well. Just on thing. I use filter with custom taxonomies. How i can do to show all posts when i click to see all ? Where i can find the array with all term ID of my category ?


    $args = array(
    'post_type' => 'dt_logos',
    'post_status' => 'publish',
    'posts_per_page' => -1,
    'orderby' => 'title',
    'order' => 'ASC',
    'tax_query' => [
    [
    'taxonomy' => 'dt_logos_category',
    'field' => 'term_id',
    'terms' => $catID,
    'operator' => 'IN'],
    ] );

    THX. Julien

  • Jet Bautista says:

    Hi Bro!

    Do you have the working file on this? I yes, can you please send me? 🙂

    Appreciate your help

  • Martin says:

    Hi,
    where do I past this code if I want to add this filter to my product archive template in Elementor?

    • Bob Weichler says:

      Hi Martin,
      Thank you for your comment. We don’t use page-builders in our projects so I’m afraid I can’t assist you with this one.

  • N says:

    Awesome. I’m looking forward to the WooCommerce blog post. Seems crazy it’s not built in by default. Thanks.

  • Jamie says:

    Love the post, well explained and easy to follow. However, I cant work out why for me it’s calling the php function twice? I’ve posted my query including the code on another forum, but no solutions as yet:
    https://stackoverflow.com/questions/66478163/why-is-ajax-running-twice-within-wordpress-function?noredirect=1#comment117525647_66478163
    Thank!

    • Bob Weichler says:

      Hi Jamie,
      Thanks for reaching out to us. This looks interesting. Can you try to echo a blank ajax call and exit?

      function filter_projects() {
        echo 'calling filter_projects';
        exit;
      }
      add_action('wp_ajax_filter_projects', 'filter_projects');

      Does it still gets printed twice in your response?

      • Jamie says:

        hi Bob, thanks for your response. I actually get a 400 (Bad Request) if I use the code you mentioned. I have to add in the second add action to get it working: add_action(‘wp_ajax_nopriv_filter_projects’, ‘filter_projects’);
        but when I do, I get the message printing twice. And if I just use the ‘wp_ajax_nopriv_filter_projects’ add action on its own, I get the message printing twice. thanks

  • Marek says:

    Great, thanks!

  • Devin Columbus says:

    Thank you for taking the time to write this post! I was wondering if you’re still planning on finishing it and/or posting the final source on Git ?

  • Simms says:

    Amazing tutorial! Very neat and works like a charm

  • Trevor says:

    Looking forward to the full example of this tutorial.

  • Iván Alejandro Guzmán Estrada says:

    Hello! I’m actually not a programmer, but I’ve been learning how to make my own website and template. I was able to follow all steps but the filter itself doesn’t do anything (yet). What is not clear to me is what you mean when you say “register the function”. I’ve googled a bit but can’t find the right method to do that.

    • Bob Weichler says:

      Hi Iván,
      Thank you for your message. I agree that it might be confusing when I said ‘register the function’. It simply means that when we write our function, we need to use ‘add_action’ to actually make it work.

      function filter_projects() {
        ...
      }
      add_action('wp_ajax_filter_projects', 'filter_projects');
      add_action('wp_ajax_nopriv_filter_projects', 'filter_projects');

      So we can write our filter_projects function, but it won’t work if we are not calling the do_action at the end.

  • Prashansa Poddar says:

    Great post! It helped me a lot on one of my projects. Did you implement an infinite scroll on filtered results as well? I have gone through your post on load more Ajax but was not sure how it could be implemented with Ajax filters. Thanks

    • Bob Weichler says:

      Hi Prashansa,
      I did not implement infinite scroll in this article yet, but you should be able to swap the ‘load-more’ trigger to trigger automatically at the bottom of the page instead of manually clicking a button. I have another article about specifically loading more posts with ajax in WordPress on my blog. At the bottom is a section of triggering the load-more on scroll. Hope it helps!

  • Reece says:

    Hello,

    First off thank you for this comprehensive tutorial. I am looking forward to using it in one of my projects as well. I have made it to the end of the first part of filtering by category. I am doing this on a custom post type such as yourself. However, I am getting the following error after clicking on the category:

    Uncaught Error: Syntax error, unrecognized expression: #!

    I also removed the href all together just for fun. In that case, I do not get any jquery error, but nothing happens.

    Any ideas? Thank you in advance. Looking forward to your GitHub repo!

    Thanks!

    • Bob Weichler says:

      Hi Reece,
      Can you post your jQuery on-click function, please? It looks like you’re targeting the a-elements by href and not by ID because nothing is happening if you remove the href tag. I use href=”#!” to prevent the page from refreshing by default. But in your js code, you should be targeting the ID and not the #!.

    • Bob Weichler says:

      Hi Tim,
      That’s a very good question actually. Adding pagination to this post is also still on my todo list, but I have the feeling this post is already pretty long (and it’s still unfinished). So maybe I will add a second blog post that will explain filtering with pagination.

      The problem with ajax filtering is that the page does not refresh. So WordPress doesn’t know that you’re filtering the posts. Simply refreshing the page will also remove our ajax filters. Try to add the current filter as a parameter on your pagination links, so WordPress knows how to filter on page-reload. If your pagination links look like this /page/2, then try adding ?filter=blue with JS to all a-tags within your pagination container. When you do that, you can check for the filter parameter in PHP and update your default query to use the current filter.

      I hope this can somewhat help you out for now. I hope to find the time to add pagination to this or a new post soon!

  • Tim says:

    Hi,

    Firstly, thanks so much for all the code. Just implemented it on my site and it’s working well.

    My site uses pagination on my blog page and right now the filters reset on each page.

    Is there a way to make sure that the pagination is only showing pages with posts with the selected category?

    Thanks,
    Tim

  • Sam Ghyselinck says:

    Loved the article, a really extensive and clear explanation! Made my filter work, just going through the steps one by one.

  • Nino says:

    Hi man!

    What do I have to change to filter on a taxonomy instead of a category? What do I have to change?

    Thanks!

    • Bob Weichler says:

      Hi Nino,
      For custom taxonomies you need to change your query to something like this:

      $projects = new WP_Query([
         'post_type' => 'projecten',
         'posts_per_page' => -1,
         'tax_query' => [
            [
               'taxonomy' => 'custom-taxonomy',
               'field'    => 'slug',
               'terms'    => array('your-term-slug'),
               'operator' => 'IN'
            ],
         ]
      ]);

      It's untested so let me know if it works correctly for taxonomies. I'll add it to the article if it does.

      • Nino says:

        Hey Bob,

        I have created a custom taxonomy themes with multiple values in the back-end. I am able to return them when looping through them. But filtering is not working. I am not using categories but would like to filter by taxonomy. I have tried to apply your code but without luck.

        
        function filter_updates() {
            $postType = $_POST['type'];
            $catSlug = $_POST['category'];
        
          $ajaxposts = new WP_Query([
            'post_type' => $postType,
            'posts_per_page' => -1,
            'category_name' => $catSlug,
            'orderby' => 'menu_order',
            'order' => 'desc',
            'tax_query' => [
                [
                   'taxonomy' => 'custom-taxonomy',
                   'field'    => 'slug',
                   'terms'    => array('your-term-slug'),
                ],
             ]
          ]);
        }
        

        Could you see what is causing this issue? When trying to filter it returns only one post.

        • Bob Weichler says:

          Hi Nino, I removed a bit from your code as it became too long. I left the query part in your answer. If you’re not using categories, you need to remove the ‘category_name’ from the arguments. The Tax Query in my previous example was filled with dummy content. You need to change the custom-taxonomy and your-term-slug with your actual taxonomy.

          • Nino says:

            Thank you very much Bob for the fast reply. I will try to explain myself. I have figured it out I able to sort based on taxonomy. I would like to use 2 SELECT for 2 different taxonomies one for themes and one for subjects which are 2 taxonomies. How can I use a SELECT for multiple tax filtering? When is select one dropdown option it will filter all over again, is there a way I can simply do this? So basically giving users the option to filter on 2 select fields instead of one.

          • Bob Weichler says:

            Hi Nino, I removed your code examples from your reply as it became a mess in the backoffice. Please use pastebin or github gists to show your code. For combining multiple filters at once, I will add an additional section in my blog post. You need to build up your $args before you make the new WP_Query. Here’s a small example of how you can build your args query. I will try to expand my post over the weekend but hopefully this snippet can get you started.

            // Set your default query params
            $args = [
               'post_type'      => 'product',
               'posts_per_page' => -1,
               'post_status'    => 'publish',
               'orderby'        => 'date',
               'order'          => 'desc',
            ];
            
            // Add each extra taxonomy query like this:
            $args['tax_query'][] = [
               'taxonomy'      => 'product_cat',
               'field'	   => 'term_id',
               'terms'         => $catIds,
               'operator'      => 'IN'
            ];
            
            // Make a new WP_Query with the $args array
            $ajaxproducts = new WP_Query($args);
      • Nino says:

        Hi Bob,

        How can I add code here? For some reason, It is just not working for me.

  • Kevin says:

    Your code is clean and easy to follow. Thank you very much

  • Marty says:

    I want to see full code. Contact me please on my email. Where did you put the files you mentioned? I also plan to put snippets instead of intrusive code injection inside functions.php. I would try to avoid putting code inside admin-ajax.php also, just beacuse of possible updates and exploits. And one comment also, I really like php more, so when possible, I would bypass the ajax. It runs faster, eats less memory. If you do a lot of JavaScript and jQueries, it’s gonna eat your resources.

    • Bob Weichler says:

      Hi Marty, I will try adding some files to a git repo to offer a full code example. Also, I am a front-end developer so I will always choose for a JS solution when possible 🙂 While with javascript I can rerun solely the query I need, running it in plain PHP will require a full page reload – reloading headers and footers ass well. I did not add any code to admin-ajax.php, but it’s the WordPress default route for ajax calls.

  • Dee says:

    Hi.
    Thank you for your work on this.
    Could you explain how this could work for post_tags?
    in the js, changed the data: category to data : taxonomy
    in functions filter_projects, changed the slug variable to –
    $catSlug = $_POST[‘taxonomy’];
    But it shows ALL the posts on the site

    • Bob Weichler says:

      Hi Dee,
      Did you also change the WP_Query from ‘category’ to ‘tag’ => $catSlug? It should work just fine for post_tags as well. Just make sure you’re using the correct WP_Query arguments. They are slightly different from categories to tags. You can find all available query arguments for tags in the beginning of my post.
      Hope it helps!

  • Bart says:

    Hello, does this also work for multiple filters? So can I copy the code that multiple filters work on the same custom post?

    Thanks in advance,

    Bart

    • Bob Weichler says:

      Hey Bart,
      Thank you for your message. Do you mean combining multiple filters or using this filter for different post-types? You can pass the parameter of ‘post_type’ to your PHP function the same way as we pass our ‘category_name’ in our example. I will edit this post with add an additional example, but hopefully, this makes sense?
      Best,
      Bob