Skip to main content

Filter WordPress posts by multiple taxonomy terms with AJAX

UPDATE: 14. Nov. 2016
Demo is now extended to show standard numbered pagination or infinite scroll.

This is the second part of the post where I show how to display and filter WordPress posts based on taxonomy term.
In this post I will create a working script which shows how to filter posts by multiple terms.

I will include the entire code but I will not explain in depth what’s going on there because I already did that in previous post.
If you missed the first part please check here How to filter WordPress posts by custom taxonomy term with AJAX and pagination.


The idea is very simple, in the previous post you can find out how to:

  1. Display list of terms
  2. Get taxonomy and term of clicked element
  3. Get and display posts by selected term

This post will just extend this feature so instead only one element, we will get all active terms and retrieve posts for all active terms.
To do that we would just need to slightly modify our javascript to:

What does this do?

  1. On click toggle parent li item class ‘active’
  2. Get all .active li items
  3. Get ‘taxonomy’ and ‘term’ slug from all elements: $terms[taxonomy_name] = ['term1', 'term2'];
  4. Pass params to our function that gets posts by selected terms

Function which gets posts by AJAX stays the same like in previous posts.

PHP function

After we get all params with jQuery we need to get posts by selected terms. To do that we need to modify our PHP function, and use ‘tax_query’ in our WP_Query.
Snippet from function:

What does this do?

Basically script stays the same like in previous post, the only difference is that we now pass data as an array of 'taxonomy' => array_of_terms[]' and we need to loop trough this array and construct ‘tax_query’.

Working demo example with pagination
Working demo example with infinite scroll


  1. PHP file Gist
  2. Javascript file Gist


    1. Hi Rubic,

      Thanks for reporting, I have fixed the issue.
      Problem was that I forgot to include ‘wp_ajax_nopriv’ so it was not working for logged out users.


  1. Hi, thank you for tutorial, when i first try your demo, its doesn’t work, but now its fixed for logged out users :)
    I wonder how to show all posts when:
    – User visit page with all posts (blog in my case) for the first time
    – User first select some terms, and after that un-select everything (return to default state)
    Also what do you think about using/adding hidden term list item, that will contain default wordpress category?
    I mean: my default wp category is Uncategorized(for testing this) and i have cat 1 to 12 as children of uncategorized
    When i make hidden list item li.uncategorized .active
    it should load all posts by default, whnever i click on other item it should remove active class from li.uncategorized .active, so my sorting should work??
    Currently i’m using barba.js pjax, so my testing website feels like its fully ajaxified, i read somwhere that pjax is better for seo than ajax, its doesn’t hide content for search engines
    But if we could load all posts by default, or load all posts when any terms aren’t selected it still should be fine for seo, yes?

  2. Hi Gabrielle,

    I’ve updated example to include ‘Show all’ which is selected by default.
    Hope this clears all your questions.

  3. Hi Vlado,

    Wonderful guide, works perfectly for me!

    Next step for me is to create infinite-scroll / load more pagination instead of page numbering to make it all ajaxified :).

    Anyone that has managed to do this?

  4. Would be amazing, I don’t think it should be a huge deal while you’re already using a sort of ajax-pagination: I just think the load more option is nicer than replacing a number of posts with new posts (page numbers).

    Meanwhile I will try to expand the filtering of posts by adding filters from meta data in select inputs. Cause im using your function with custom taxonomies to filter apartments by “Place”. Now i need to add “No of rooms” and “Min/Max-price” in select dropdowns. I guess by adding some kind of meta_query key->rooms type of thing.


    1. Hi,
      Article is now updated to work either with infinite scroll or numbered pagination.
      Please check the demo with infinite scroll here and update your code accordingly.
      I’m not sure are you using shortcode or not, but to use infinite scroll you would need to add param pager=”infscr”, that would display ‘Load more’ button instead of numbered pagination.

  5. Hi Vlado,

    I’m also intarasted in this infinite-scroll option.

    Could you help me with query for time.
    I would like to user can filter by X h ago for example …

    Thanks Nikola

    1. Article is now updated to work either with infinite scroll or numbered pagination.
      Please check the demo with infinite scroll here and update your code accordingly.
      I’m not sure are you using shortcode or not, but to use infinite scroll you would need to add param pager=”infscr”, that would display ‘Load more’ button instead of numbered pagination.

      Regarding the time query, please consult WP_Query documentation on codex, the best way is to trial and error until you get desired results on your localhost.

  6. Hi Vlado,

    could you please check your files on github.
    I really tried all but there are some differences between codes on post / github and your working code :) .

    First of all in any file i cannot find this piece of code for example:

    <a href="#" data-filter="taxonomy; ?>” data-term=”all-terms” data-page=”1″>
    Show All

    so i need to search inside post to see what changes you have made and that is not problem,
    problem is that doesn’t work after that for example on infinity scroll i get infinity scroll and i get paging, when i click load more it reload second page with posts, and if i use pager=”infscr” it just show me pagination :)

    If you can make resume with final files which you use on your site it would be very nice!

    I will really appreciate your help and thanks once again for this great tutorial!


  7. Hey Vlado Bosnjak,

    I am using tag with options for the filter selection. How can i make your code work with my markup?

    $(‘.sc-ajax-filter-multi’).on(‘click’, ‘a[data-filter], .pagination a’, function(event) {}

    I have tried replacing a[data-filter] with option[data-filter] doesn’t work. Console log returns nothing

    Please help!

    1. Hi Dylan,

      Sorry but I’m not sure I understand whats wrong.
      If you are using select, then you probably should do:

      $('select').change( function() { ... })

      To get the selected option

  8. Great tutorial, I learned a bunch here that directly applies to the project I’m putting together. Thank you!

    I have a question about setting the specific terms to be used with // ‘terms’ => false, // Get specific taxonomy terms only

    Is it best practice to use the term_id? I suppose it would be because if you changed the name then the id would still be maintained? If desired, would it be easy enough to change the list of terms to names or slugs?

    thanks again.

    1. Also, for some reason I can’t get the terms attribute to work. Is the supplied code using that attribute?

    2. Hi Stephen,

      Yes, I think by ID would be best.
      You can get entire term object and then display what you like.

  9. great ! I love this tutorial and codes.but could you please tell me how to show category name instead of category slug in the selection confirmation msg section? thank you very much

    1. what I mean is the code here as : foreach ($tax[‘terms’] as $trm) :
      $msg .= $trm . ‘, ‘;

    2. Hi Eric,

      I’m not sure where exactly is this code, but if $trm = slug of the term, you can get term data with get_term_by function. Anyhow, you can do print_r and see what is $term.

  10. Hy Vlado,

    awesome posts – it pushes my concept in the right direction! But what if i have to filter in multiple taxonomies. In my case i have to filter a recipe database ( One of the taxonomies is “season”, another is “ingredients”. I have to combine them in a way like in this example ( Do you have a idea how to achieve that?

    Thanks for your reply and kind regards,

    1. Hi Bernhard,

      Basically it’s the same thing. Just instead of 1 tax add array.
      In the shortcode part here L8:

      $a = shortcode_atts( array(
              'tax'      => ['tax_1', 'tax_2], // Taxonomy
              'terms'    => false, // Get specific taxonomy terms only
              'active'   => false, // Set active term by ID
              'per_page' => 12 // How many posts per page,
              'pager'    => 'pager' // 'pager' to use numbered pagination || 'infscr' to use infinite scroll
      ), $atts );

      Then, on L16, you don’t do get_terms($a['tax']);, but instead you do:

      foreach ($a[tax] as $tax) {
         ... the rest of code ...

      That’s roughly, but in short you just need to convert my code for one taxonomy into for/foreach loop.
      On click of each term, you mark him active and when making request get all with class ‘active’ and read tax/term_id from them, and construct WP_Query out of it.


  11. This is a life-saver!!! I have more complex taxonomy terms, but at least this gets me started! I was never able to figure this out on my own and now I can ajaxify ALL the things! Thank you so much!!

  12. amazing tutorial just one question, how can i use 2 post type and create 2 shortcut of them for echo in different area’s like :
    echo do_shortcode('[ajax_filter_posts_moves per_page="10"]');
    that show post’s from moves post type and :
    echo do_shortcode('[ajax_filter_posts_series per_page="10"]');
    and this one show posts from series post type
    p.s:sorry for bad english hope you understand my question .

    1. really thanks for tour answer sir , can you explain more? duplicating shortcut is easy the problem is how i can tell the program if post_type = movies echo ajax_filter_posts_movies and next time put post_type = series and echo ajax_filter_posts_series there is a way or no way to do this?

    2. Purpose of this tutorial is to show you how things work so you can use the logic and create what you need for your self.
      It would be difficult for me to include all possible variations that would cover all available cases.

      What you need to do is to extend shortcode function to accept one more param ‘post_type’, and pass that param along with all others to the function.
      It really shouldn’t be that hard.

  13. Hi, great tutorial. I think there’s a small bug for the infinite scroll demo though.

    When you’re on the “Show All” tab and click “Load More”, the article “Add Wistia videos …” shows up.

    Then if you click on another tag and then click back again on “Show All” and then “Load More” again, another article populates, “Pre-populate Woocommerce…”. So the “Show All” functionality is not resetting itself. Once you have loaded new posts once, they are gone forever (unless you refresh the page).

    I am trying to fix that but haven’t been able to yet. Any ideas?

  14. Hi Vlado,
    Excellent tutorial, great work!

    However there is an issue with the infinite scroll (I tested on your demo too, just to be sure it was not coming from me).
    Basically, it doesn’t reset to the “Load more” link to “#page-2” when you change of tag. For example on your demo, stay on “show all” and then navigate to #page-5; then pick another tags (less than 5) and try to click on “Load More”, it will be set with “#page-6” and will show “You reached the end”.

  15. Hi Again,

    I fixed the issue I just reported, I placed the ( condition after the else {}.

    * Append content for infinite scroll
    else {
    if ( !== 0) {
    $pager.attr(‘href’, ‘#page-‘ +;

    1. Thanks Nico,
      I will review the code as soon as I get some free time.
      I’m sure it worked when I was testing it :)

      Glad you found your own solution, it fulfills the purpose of this tutorial.

    2. Mhm. I have just checked this and it’s working for me.
      Would you mind sharing what browser and version are you using?

  16. Hi,

    Using Chrome Version 56.0.2924.87. + FF 52.0.2
    Do this little experiment:
    1. Go to
    2. “Show All” is selected, click on “load more” twice (mouse hover the “load more” button and you will see next link is “#page-4”)
    3. Select 2 another tags, you will see the first one, then click “Load more” to see the second one and you will get “You reaced the end” message because it’s trying to load “#page-4” instead of “#page-2”
    4. Also, if you go back to “Show All”, you get to see the first post again, but when you click on “Load more” it will bring the fourth one (“#page-4”), skipping post 2 and 3.

    Hope it make sense :)
    Thanks again for your tutorial!

    1. Hi John,
      Anything is possible :)
      But not as a copy/paste from this tutorial, it shows how thing work so basically you need to alter the code a little bit to target specific container of posts.
      It is still almost the same function that triggers query, in your case you would need to display results in correct container.

      Goal of tutorial is to explain how things work and not only to copy/paste things.

  17. Hi Vlado!, I am trying to exclude terms by id with ‘exclude’ => array(96), but it does not work. (Show all tags)
    function vb_filter_posts_sc($atts) {
    $a = shortcode_atts( array(
    ‘tax’ => ‘tagss’, // Taxonomy
    ‘terms’ => false, // Get specific taxonomy terms only
    ‘exclude’ => array(96),
    ‘active’ => true, // Set active term by ID
    ‘per_page’ => 12 // How many posts per page

    I have also tried including unique id by terms

    function vb_filter_posts_sc($atts) {
    $a = shortcode_atts( array(
    ‘tax’ => ‘tagss’, // Taxonomy
    ‘terms’ => array(’96’), // Get specific taxonomy terms only
    ‘active’ => true, // Set active term by ID
    ‘per_page’ => 12 // How many posts per page

  18. Hello!

    This tutorial is great. I was wondering how hard it would be to alter the code slightly to hook into a main query instead of creating a new query? This way you can filter an existing blog/custom post type query without having to recreate it.


    1. Hi Jesse,

      I usually hook into main query using pre_get_posts filter, but I’m not sure that would be useful.
      Not sure why would you want to do that, this is a standard WordPress way of doing things.

    2. Someone may want to do this if they are using your tutorial in a more advanced setting there they aren’t creating and updating (filtering) a loop, but rather updating (filtering) an existing loop. Yes, using WP_Query is WordPress’ standard way of creating a new loop query, but there are instances where there are existing loops that are created by plugins or themes, and implementing your AJAX filter would be more flexible and customizable over a pre-existing AJAX filter plugin.

      I suppose you didn’t have enough context to understand. Seems like you are unable to provide direction on this, but thank you anyways.

  19. Hi Vlado, thank you for the fantastic tutorial; it is working well for my application, and I’m trying to make a few customizations. Mainly, I’m wondering if you could elaborate on how to add multiple taxonomies by passing the array of taxonomy names to `tax` in the `shortcode_atts` portion of your code, as you mentioned in your reply to the earlier question by Bernhard? In my case, I have two custom taxonomies I’d like to use, one hierarchical and the other non-hierarchical, and I’m assuming that using the hierarchical taxonomy requires passing as an array as well? Thank you for any further direction you’re able to provide.

  20. Hi Vlado,

    Thank you so much for your time and effort to create such a complete and comprehensive tutorial. I’ve been spending months trying to solve the problem with my ajax filtering and pagination without much success, and bam! Google lead me to your site! You are my life saver!

    One thing, I am using Select2 jquery for my dropdown list instead of your Tag Cloud setup, so I am unable to use Document on Click event in the get_posts function. The events available for Select2 are pretty limited, Select, Selecting, Change. Any suggestion on how to bind this event to the Click event in the Pagination div? I tried the below, but it didnt work:
    jQuery(‘#image_grid’).on(‘change’, ‘#tag_search, #date_search, #pagination’, function(e) {

    1. Mhm, that’s strange that they don’t have ‘init’ or ‘load’ event.
      You can attach maybe to select2 event on change, and then manually trigger change event on your select item. Eg:

      $('#my-select').on('select2:change', function (evt) {
      // Trigger element

      Or you can simply create WP_Query in your template that will display posts on page.


    1. OK, it is working, after adding this.

      wp_create_nonce( ‘bobz’ ),
      ‘ajax_url’ => admin_url( ‘admin-ajax.php’ )
      add_action(‘wp_enqueue_scripts’, ‘assets’, 100);

      I am a little confused what the above does though. I have added it exactly as above, and it works. I had assumed I would need to amend this to point to my own jquery file? If so, how come it is working?

  21. Hello.
    Thanks for the great job with code ;) … but I think I found some bugs.
    When You select few terms and use button “Load More” to show all of filtered terms, and after that deselect used terms and select them once again or something else, button “Load More” returning “You reached the end”, not new terms.
    Please check this out.

    1. Hi Michael, Thanks for feedback. I will check the evil bugs and remove them, thanks a lot for testing.

    2. Hello again.
      Did You findout how to fix infinite scroll? I found another bug. At first I wanted to hide content at moment when no one post is find (‘status’ => 201), cause when You’re method is ‘pager’ and You select combination of terms that is giving ‘No posts found’, the old content with pagination is still visible under filters and when You try to click for example second or third page, pagination don’t work for this old content. It was not logical for me so I thinked it’s better to hide this. Unexpectedly I found code for this in Your JS script, but here is the problem:
      else if(data.status === 201)
      if(data.method === ‘pager’)
      … the rest of code …

      data.method here is ‘undefined’ so $content.html(data.message); is not working.
      Also I added checking of code 501. I wanted to hide the content as above when ”Term doesn’t exist”. And here data.method is also returning ‘indefined’. It seems that data.method is correctly detected only with ‘data.status === 200’. With method ‘infscr’ everything works just because there is no detection of ‘data.method’, its just using ‘else’. Please check this also. I’m looking for a few hours to solve this but I could’t find solution. It’s to hard.

    3. I did it ;) I just add ‘method’ => $pager in functions.php, to else with ‘status’ => 201. Now it’s working perfect. Ufff…
      But still no progress with infscr.

    4. Hi Michael, great to hear that.
      I will try to get some time to fix it up in a day or two.

    1. @KS
      I missed something? Where is your earlier comment?
      The bug with infinite scroll still exist.

    2. Michael,

      My earlier comment wasn’t about infinite scroll at all. I made a comment about an entirely different, and then followed it up shortly afterwards when I realised I had made a mistake and fixed it.

  22. I fix the bug with infscr.
    Just add this below after success in js file:
    $pager.attr(‘href’, ‘#page-2’); // Added to fix infscrl

  23. Is it possible to have multiple instances of this?

    For example, ‘post type a’ on one page, and ‘post type b’ on another?

    Maybe I’m missing something but I can’t get it to work?

  24. I just realized I posted this under your first post by accident. It was meant for this one, so I’m re-posting my comment:

    Hi, this is really great! I have it working for the most part; however, I need to create a 2 column list of categories (specifically children of 2 categories) that filters posts for a specific custom post type and need some help.

    (1) I’m using WP’s default categories and need to be able to limit the categories and posts shown to the children of specific categories and not a list of all categories/all posts from all categories. How do I do this?

    (2) Is there a way to change it so that it displays checkboxes/uses select functionality?

    You’re help is greatly appreciated!

  25. Is there anyway to have the page scroll to the top when clicking either of the pagination links? For some reason, the usual methods of achieving this are not working.

    1. Yes, you should be able to specify it in shortcode, if you are using shortcode to display it. [ajax_filter_posts tax=”category” terms=”abc”]
      Check the code for shortcode, there are params for default display, or you can extend it on your own to suit your needs.

  26. I think there is something that I’m missing, Right now the only way I can get the first page of results to load is to use jquery to force a click on all. Is there a way to get it to load all without forcing a click? Does the script load results by default?

  27. Good afternoon.
    I hope someone can help with this, since it is very frustrating
    I want to create a page in which there is a dynamic table that when choosing a 2 different type of taxonomies and automatically appear 2 post_type that are related. I created the queries to find the diferents taxonomies and post type, but I need to be able to combine everything in a simple search, like sql “inner join”.
    There are some plugins that in theory help to create these relationships, but they don’t work, so I want to know if someone has already developed it
    I can create three drop-down lists. One for one taxonomie, one for other taxonomie and one for one post type. When i select all three values then using an Ajax function i can not call the shortcode with the values of taxonomie1_id, taxonomie2_id and posttype1_id to select the posttype2 content
    Thank you. Regards.

    1. Plugins would never solve this type of problem.
      So you want to filter post_type2 by: post_type1, taxonomy1 and taxonomy2?
      Can you provide example with real-life data?

  28. I have two taxonomies (season – league) and two post type (teams – players). I want to get the list of players of a particular team in league and season

  29. Hi Vlado,
    Your tutorial is great but being new I am sorta lost and not able to get things to work the way I would like. If it is possible to pay you for making up some code which lists three custom taxonomies in a sidebar with checkboxes organized under the Parent as seen here:

    I can not seem to get the plugin to work I purchased so I am trying to do my own. I want all the events to be shown then allow users to click as many checkboxes to get their results which should load with ajax on same page like you have here with pagination.

    I tried to do it with your code but all I see is the shortcode on my page at
    I have taken the js off for now but could add back for you to look at.

    I would really like to learn how to do this but I am sure you may not have the time to teach but I am willing to pay something from my tight tight budget for your help.

    Thank you for your time.

  30. Hi Vlado,

    This is a really great tutorial. I’ve been looking for something like this for a current project. I’ve just spent the last few days implementing this with some success. I’m unable to provide the preview link for legal reasons but it’s basically a display of partner logos with a single parent category and three child subcategories. There are also tags (some shared) associated with each partner. The multi-filter is working great between the categories. I was hoping you could point me in the right direction with a few things – my brain is a little exhausted lately and maybe I am just missing something.

    I am able to:
    – Display the parent category on page load (sub categories displayed automatically).
    – Adjust the query to order by Title, order ASC, and only use a single ‘cat’ => catID.
    – Adjust the loop to display the featured image thumbnail and associated tags for each partner.

    I am having trouble with:
    – Displaying Tags in addition to the Categories.
    – Excluding empty Categories from the list.
    – Displaying Tags related to the Categories displayed.
    – Breaking up the Categories and Tags in two separate elements/blocks (divs, ul, etc.)

    Below is a link to see the Category list displayed above the partner logos. I am trying to have the Category (Partners) and Subcategories (Data, Industry, Inventory) in one element/block and the respective Tags in another element/block.


    1. Hi Rami, I’m glad you found it helpful.
      All your questions you should solve in the part with ‘WP_Query part‘.
      I would suggest you to create a blank page template and make WP_Query return correct results there and then implement your own query.

      – Displaying Tags in addition to the Categories.
      VB: For this you need to get and return tags for each post in the loop

      – Excluding empty Categories from the list.
      VB: There should be option for that:

      – Displaying Tags related to the Categories displayed
      VB: Tags are not related to categories in anyway they are another taxonomy (‘another category’), you never assign tag to category, but instead you assign both to posts only

      – Breaking up the Categories and Tags in two separate elements/blocks (divs, ul, etc.)
      VB: You would need to reorganize the response you return based on tags/categories but it’s hard for me to say without seeing actual code.
      Don’t give up :)

  31. Hi bobz, your code working perfectly can suggest my how to add multiple attribute (taxonomy ) filter also

    Like Example


    []cat 1
    []cat 2


    Attribute Taxonomy 1
    [] attt_1(4)
    Attribute Taxonomy 2

    all are multi-select checkbox how can i do it pls suggest

  32. Hey!

    Maybe i’m stupid, but i can’t handle three things…
    I even managed to open taxonomies.php in wp-includes, messed there a bit and still i wasn’t able to fix:

    1. Hierarchical displaying:

    So, my tree looks like this:


    and so on. The thing is, after trying to include my custom code, i can only display categories ASC, but without parent -> children relation. Parents should not be ‘clickable’.

    2. After changing tags to caterory, show all button doesn’t work. I even tried looking for a fix in your previous posts. Thing is, Show all button is clickable, but not anything.

    3. Is there a chance to display ALL categories, even empty ones?

    For your information, i’m running your lastest code from GIST.

    Thank you!

  33. Hi Bob, I copied the files from your GIST (thanks by the way!) but when I try to implement i get a console error “bobz is not defined”. On the page of my site it shows the post tags, but just says “loading posts…”. i didn’t change anything on the files either. any idea what went wrong?

  34. I got this working! I have a question though. This demo page:, has toggles. it just adds more terms to the results rather than switching between them. This one: does the more expected behavior of only showing the results of the term i clicked. I would like to use the 2nd option but I’m not sure which code to use. The one from this page is the 1st option but seems newer. does it have an option to change the behavior to model the 2nd?

Leave a Reply

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

Please don’t paste any HTML, Js or PHP code into comments box, use or similar service to share code examples.

Characters left: 1000