How to Combine AJAX load more button with AJAX filters
As usual I work with one of the default WordPress themes – Twenty Seventeen, why not. I would like this post to be clear for you, so I split it into steps – Step 1 is all about HTML, Step 2,3 is all about JavaScript and Step 4 is all about PHP.
To understand how it works, watch this video:
Step 1. Creating «Posts per page» and «Order by» filters. Load more button.
First of all make sure that all the posts on the page are wrapped into the container, but the load more button and the filter form are outside of this container. Example:
<!-- Filters will be here -->
<div id="misha_posts_wrap">
<!-- Posts will be here -->
</div>
<!-- Load more button will be here -->
Filters
I decided to make this code simple and use only two dropdown selects – for posts per page and for ordering posts. If you’re interested in sorting posts by meta values or by categories (taxonomy terms), I recommend you to look at this tutorial for more information.
<form id="misha_filters" action="#">
<label for="misha_number_of_results">Per page</label>
<select name="misha_number_of_results" id="misha_number_of_results">
<option><?php echo get_option( 'posts_per_page' ) ?></option><!-- it is from Settings > Reading -->
<option>5</option>
<option>10</option>
<option value="-1">All</option>
</select>
<label for="misha_order_by">Order</label>
<select name="misha_order_by" id="misha_order_by">
<option value="date-DESC">Date ↓</option><!-- I will explode these values by "-" symbol later -->
<option value="date-ASC">Date ↑</option>
<option value="comment_count-DESC">Comments ↓</option>
<option value="comment_count-ASC">Comments ↑</option>
</select>
<!-- required hidden field for admin-ajax.php -->
<input type="hidden" name="action" value="mishafilter" />
<button>Apply Filters</button>
</form>
You can find some CSS styles in the child theme, link is under the video above.
Load more button
If the code below doesn’t work for you, try to add global $wp_query;
before.
if ( $wp_query->max_num_pages > 1 ) :
echo '<div id="misha_loadmore">More posts</div>'; // you can use <a> as well
endif;
In this case I do not recommend you to use another form of conditional statement because there could be conflicts in theme.
Step 2. Script connection, passing parameters to wp_localize_script()
wp_localize_script()
function is the awesome thing that allows you not only to translate strings in your JavaScript code but also pass the dynamically generated parameters in it.
So, nothing especial below – we just use WordPress default wp_enqueue_scripts
action hook to include our script.js
and pass some parameters there (yes, create please script.js
somewhere, I did it in the theme directory).
add_action( 'wp_enqueue_scripts', 'misha_script_and_styles');
function misha_script_and_styles() {
// absolutely need it, because we will get $wp_query->query_vars and $wp_query->max_num_pages from it.
global $wp_query;
// when you use wp_localize_script(), do not enqueue the target script immediately
wp_register_script( 'misha_scripts', get_stylesheet_directory_uri() . '/script.js', array('jquery') );
// passing parameters here
// actually the <script> tag will be created and the object "misha_loadmore_params" will be inside it
wp_localize_script( 'misha_scripts', 'misha_loadmore_params', array(
'ajaxurl' => site_url() . '/wp-admin/admin-ajax.php', // WordPress AJAX
'posts' => json_encode( $wp_query->query_vars ), // everything about your loop is here
'current_page' => $wp_query->query_vars['paged'] ? $wp_query->query_vars['paged'] : 1,
'max_page' => $wp_query->max_num_pages
) );
wp_enqueue_script( 'misha_scripts' );
}
A little more description of this process you can find in the post about load more button.
Code to functions.php
.
Step 3. The Load More and AJAX Filtering Script
It is much simpler than you think. Here it is:
jQuery(function($){
/*
* Load More
*/
$('#misha_loadmore').click(function(){
$.ajax({
url : misha_loadmore_params.ajaxurl, // AJAX handler
data : {
'action': 'loadmorebutton', // the parameter for admin-ajax.php
'query': misha_loadmore_params.posts, // loop parameters passed by wp_localize_script()
'page' : misha_loadmore_params.current_page // current page
},
type : 'POST',
beforeSend : function ( xhr ) {
$('#misha_loadmore').text('Loading...'); // some type of preloader
},
success : function( posts ){
if( posts ) {
$('#misha_loadmore').text( 'More posts' );
$('#misha_posts_wrap').append( posts ); // insert new posts
misha_loadmore_params.current_page++;
if ( misha_loadmore_params.current_page == misha_loadmore_params.max_page )
$('#misha_loadmore').hide(); // if last page, HIDE the button
} else {
$('#misha_loadmore').hide(); // if no data, HIDE the button as well
}
}
});
return false;
});
/*
* Filter
*/
$('#misha_filters').submit(function(){
$.ajax({
url : misha_loadmore_params.ajaxurl,
data : $('#misha_filters').serialize(), // form data
dataType : 'json', // this data type allows us to receive objects from the server
type : 'POST',
beforeSend : function(xhr){
$('#misha_filters').find('button').text('Filtering...');
},
success : function( data ){
// when filter applied:
// set the current page to 1
misha_loadmore_params.current_page = 1;
// set the new query parameters
misha_loadmore_params.posts = data.posts;
// set the new max page parameter
misha_loadmore_params.max_page = data.max_page;
// change the button label back
$('#misha_filters').find('button').text('Apply filter');
// insert the posts to the container
$('#misha_posts_wrap').html(data.content);
// hide load more button, if there are not enough posts for the second page
if ( data.max_page < 2 ) {
$('#misha_loadmore').hide();
} else {
$('#misha_loadmore').show();
}
}
});
// do not submit the form
return false;
});
});
At this moment if you try to use the filter form or the load more button your script should print 0
, if not – you made a mistake somewhere.
Step 4. Process the AJAX Request in wp_ajax_ Action Hook
To the functions.php
:
add_action('wp_ajax_loadmorebutton', 'misha_loadmore_ajax_handler');
add_action('wp_ajax_nopriv_loadmorebutton', 'misha_loadmore_ajax_handler');
function misha_loadmore_ajax_handler(){
// prepare our arguments for the query
$params = json_decode( stripslashes( $_POST['query'] ), true ); // query_posts() takes care of the necessary sanitization
$params['paged'] = $_POST['page'] + 1; // we need next page to be loaded
$params['post_status'] = 'publish';
// it is always better to use WP_Query but not here
query_posts( $params );
if( have_posts() ) :
// run the loop
while( have_posts() ): the_post();
// look into your theme code how the posts are inserted, but you can use your own HTML of course
// do you remember? - my example is adapted for Twenty Seventeen theme
get_template_part( 'template-parts/post/content', get_post_format() );
// for the test purposes comment the line above and uncomment the below one
// the_title();
endwhile;
endif;
die; // here we exit the script and even no wp_reset_query() required!
}
add_action('wp_ajax_mishafilter', 'misha_filter_function');
add_action('wp_ajax_nopriv_mishafilter', 'misha_filter_function');
function misha_filter_function(){
// example: date-ASC
$order = explode( '-', $_POST['misha_order_by'] );
$params = array(
'posts_per_page' => $_POST['misha_number_of_results'], // when set to -1, it shows all posts
'orderby' => $order[0], // example: date
'order' => $order[1] // example: ASC
);
query_posts( $params );
global $wp_query;
if( have_posts() ) :
ob_start(); // start buffering because we do not need to print the posts now
while( have_posts() ): the_post();
// adapted for Twenty Seventeen theme
get_template_part( 'template-parts/post/content', get_post_format() );
endwhile;
$posts_html = ob_get_contents(); // we pass the posts to variable
ob_end_clean(); // clear the buffer
else:
$posts_html = '<p>Nothing found for your criteria.</p>';
endif;
// no wp_reset_query() required
echo json_encode( array(
'posts' => json_encode( $wp_query->query_vars ),
'max_page' => $wp_query->max_num_pages,
'found_posts' => $wp_query->found_posts,
'content' => $posts_html
) );
die();
}
And I want to remind you one more time – if code seems complicated for you, just download Twenty Seventeen child theme with the ready functionality, link is under the video at the beginning of this post.

Misha Rudrastyh
Hey guys and welcome to my website. For more than 10 years I've been doing my best to share with you some superb WordPress guides and tips for free.
Need some developer help? Contact me
Thank you Misha. I will take a closer look
Thanks Misha,
The loadmore function is working perfect, but the filter is not working properly.
The loadmore function is using the correct query (in my case, the query of my “recipes” custom-post-type archive), but the filter function seems to be ignoring the current post-type being queried and therefore is bringing “posts” back.
Hi Luis,
Did you set
post_type
parameter in misha_filter_function() ?I didn’t wanted to have a hardcoded post_type parameter, so I solved the issue by modifying the function to get the current query post_type, which is a good idea unless you want to add a filter for post_types to the form.
Thanks for the tut Misha
You’re welcome :)
Thanks for this awesome tutorial Misha! I referenced the files attached and it works perfectly when filtering between categories and tags.
Where in my code can I go about echoing ‘No Posts Found’ text when no results are found with the filter?
Welcome! I’ve just updated the post, lines 65-66 in Step 4.
Thanks Misha for this beautiful tutorial. My ajax filter not work with load more button please help for Filter work with load more button.
Hi,
Please describe me how it doesn’t work? What happens?
Hi,
Checkbox’s not clickable after that i replace my post filter within your Twenty Seventeen. Take a look how theme working now at provided link. Thanks
http://www.cbleu.net/sites/farooq/
These are checkboxes, not radio buttons – start with removing the same
name
attribute.Too bad the you had discard my previous comment.
Also, you have PHP Object Injection vulnerability in your code.
https://www.pluginvulnerabilities.com/2017/01/09/vulnerability-details-php-object-injection-vulnerability-in-post-grid/
https://pagely.com/blog/2017/05/php-object-injection-insecure-unserialize-wordpress/
I never discard you comments. I discard comments only if user leaves large amount of unformatted code.
Maybe it is better to replace
serialize()
withjson_encode()
but you can see that the results passes to safequery_posts()
function.Strange :)
I’ve proposed you to unset:
if you plan to dynamically change title, wordpress will return it from search page, not archive.
Also, I’ve replaced
and
by
and
and sleep well after that :)
Thank you for suggestion! Unfortunately if I replace
serialize()
withjson_encode()
, it just break the code 🙃 But I will look how else this could be achieved.Thanks again!
Everything is fine :)
Hmm, now it works, strange :) I made replacements in this post. Thank you for your comments and assistance with the code!
Thank you Misha, with this one at least the ajax is firing (see comment on ajax filter post).
However, I am doing a post filtering on a custom post type, where I have to rewind posts during the query and also this loop is inside the main query on a page, so I believe I have to reset the query and the main query will be finished after the custom post type list.
I added my code here: https://github.com/resqonline/cpt-filter-test
with only the ajax filter from your previous post, I put the output of the filter in a different div and hid the initial div, not sure if I have to do this again?
Everything seems ok in your code. I have to look on it on live website to say more exactly.
maybe the json ‘content’ => $content has something to do with it, that the filter doesn’t succeed? I have to check, will get back to you ;)
Hi, thanks a lot for your tutorial, I don`t know why, but in my case it works, but loaded posts displayed without excerpt.
Hi Simon,
how you display excerpts?
Hi, thanks for fast reply!) There the custom function for display excerpts, but when I use default the_excerpt(), it didn`t works too. Example of custom excerpt function
Seems ok, did you try it with
query_posts()
or withWP_Query
?query_posts();
I recommend you to create your own excerpt function from
$post->post_excerpt
data.Hello Misha,
One of the best tutorial!!Thank you for above.
I have a question with respect to filter which you made for date and comments.
When i filter the posts , and try to use load more posts, it does not work as expected.
If I set filter w.r.t. date in DESC than posts order is affected for normal display but when i hit load more same posts are repeated ( regular order with load more posts works fine)
Any help much appreciated!!
Hello,
Play with the ready child theme (link to download) and you will find your errors.
I think it can be something with post offset, cause paged param is used properly, increasing it, but same posts continue to appear every time. Problem seems to start after using filtering function and attempt to load more posts with button.
Got same problem, when tested pure code downloaded from this article.
Still searching where is the problem
Hmmm… I will test it too.
Hi again. Will there be changes for correct work?
Thank you.
Hi,
Sorry, still have no time to test my child theme…
Hello.
Is it possiblel to made ajax pagination with filters, not loadmore button?
Will be thankfull, if u can show me the way
Hi Andrew,
Yes of course, but I have no ready code, unfortunately..
Hi again. Is it possible to make a deal for this?)
You’ve already asked about it – yes, it is possible 🙃 If you mean to ask me for a custom code work, currently I have no time
Amazing Tutorial, I always Like and see your blog.
Sir Please Create another Great tutorial for us:
Tutorial Name: “Filters Posts By Categories With Beautiful Numeric Ajax Pagination”.
Probably Bootstrap Pagination.
So when user filter the posts by category, then posts of that category should be displayed along with its own ajax based pagination.
Thanks
Hi,
Yes, I’m agreed with you, there were many requests of this tutorial. I will publish it the next.
hi Misha. it’s such an awesome tutorial. can you show me how to load more with taxonomy filtering. Thanks
Hi Ryan,
Thank you!
I recommend you to check this tutorial.
Here is my code. It’s not working. It seems it loads the posts of all categories when I click load more button. It ignores the selected categories.
What is the reason of using
unserialize()
?I’m curious what is the way to do this for numbered pagination. I can’t seem to find any answers online.
It will be in the next tutorial on my blog 🙃
Hello! Firstly thank you for this tutorial. I am so happy to have found your website.
I have added the load more and filter to my custom post page, this is archive-recipes.php. When you first load the page it loads the recipe post types fine and the load more works as expected. However, when I filter the page by category type the load more doesn’t seem to load the rest of the ‘filtered posts’ except it loads any post from the ‘recipe’ post type. Upon inspection, I notice that the following line of code in the misha_loadmore_ajax_handler() function isn’t updated when you click filter. When I check the value of this just after I have clicked filter ‘NULL’ is retrieved.
Here is the code I have used based on your tutorial:
Do you have any advice/suggestions as to where I went wrong?
Many thanks in advance.
Hello.
It`s again me, bothering you)
will you repair code for properly working filters with load more?))
Many thanks and have a nice day
Hey,
I’ve tested it one more time – works for me :)
How to create a json API for my custom post type?
Hi there,
Great post first of all!
I have mine up and running fine but am struggling to get the initial screen which loads all the posts prior to clicking on a filter. I have the same wp_query running inside my div which displays the first 4 posts fine, but when i click load more it doesn’t seem to work.
Im guessing it’s something to do with not including paged in the args? It’s fine when i click onto the filters and it uses the ajax function, but it’s just that initial query that i’m having trouble with. Below is my code:
Thanks!
Hey Will,
There is no short answer for it and you code seems ok. You can contact me by email and I will try to help you.
Hey Misha, would love to know if you’ve ever tried to implement this with two drop downs, one with the parent taxonomy, then the second conditionally showing the selected parent’s child taxonomies?
Hey,
Yes, it will required one more AJAX request, but I have no ready code for you, sorry.
Hi Misha ,
Great tutorial , I am having a lot of problems to get it to work, currently i am stuck at the loadmore functionality, when i click Load more it loads the whole page (with navigation , existing posts…) bellow is my code :
ps : I am using OOP for my scripts
below is my function Handler
Never mind I found the issue, it was a silly mistake of typing the wrong url in my ajax
one quick question : why did you use query_posts( ); instead of WP_Query()?
Hi,
Because sometimes
get_template_part()
doesn’t display the post information correctly in case of WP_Query usage.Hello Misha, first of all, thank you for these series of tutorials.
I’ve been able to filter a custom post type called “concerts” which is displayed on the page using taxonomy. These taxonomies are divided in continents. I could get a good working form mixed with this post and 3 Steps for Creating AJAX Post Filters.
Working great, but I have a problem though. Events are displayed in accordions elements which stop working when the filters are applied. Everything looks ok but when I click on the headers nothing happens.
I have a template called ajax.php on the root, is the one function is calling in order to display the events properly.
get_template_part( ‘ajax’, get_post_format());
I tried to hard code materialize script in ajax.php but still not working!!
And how can I reset the form to display all posts again?
Thank you in advance!
Jeffe
Hi,
Could please provide the part of JavaScript code which makes accordions elements clickable.
Hi Misha, I realised later that I replied opening another comment, sorry.
I’ve been trying to figure out, but I had no success!
Good news is I finally finished the implementation of your code and now filtering and load more button are working great with my custom post and the taxonomy.
I’m still heaving this issue with the opening accordion. I thought first it could be the wrapper or the external file I’m calling to populate the events but it’s not the problem.
As I mentioned in the other text, accordion in my case is controled by materialize script. This is what I’ve found inside my main.js, I hope it helps.
Thank you again!
Jeffe
All right done!!
The final result does not account to the collapsible function
It’s a matter of inserting the function again
I only have a question though, pagination it’s not working naturally with “misha_loadmore_ajax_handler” , I have to force pagination with this one
Tnks!!!
Great! I’m glad you’ve figured it out!
Pagination takes the default value from Settings > Reading. How much posts are set there?
Hi misha, one question please! Pagination is always returning 1, that’s why load more was not showing. Any idea to correct that?
Thank u!
Hi Misha, all good in settings.
I’m echoing pagination, always returns 1 and even with the pagination 1 button is appearing, it should be only tested with this line
, right?.
The best result for me would be load more only after filtering, but is not happening, or to find a condition to not show load more bellow all posts.
Tnks!!
I’m really not sure what is there, if you need my help, please contact me, I will look at your whole code and will try to help you.
Hello,
Thanks for your great tutorial. However I get stucked at some unusal points:
1- On initial page load post contents not being loaded until I click “Apply filter”.
2- On click “More posts” button it keep only showing “Loading…”
The filtering process is working as normal except for the mentioned issues.
Hello Misha,
thank you very very much for your code sample(s), even so i struggled a bit to integrate it at first with the twentyseventeen dependencies, i then had to change one line to get it actually working at all:
Is that possible? Maybe because i work within a wordpress network/multisite environment? I was already testing 4-5 other solutions this afternoon, before returning to yours (because it looked the cleanest!) after i read the last entry here:
https://stackoverflow.com/questions/44878182/admin-ajax-in-wordpress-is-not-working
Still i have to fix a few issues, but mainly it works now, hoepfully i am getting the custom-taxonomy part working as well, if not, may i contact you for payed support?
Very much appreciated this post!
Best,
bennyb
Hi Awesome Misha :) I love your code and its awesome and also can you help me to output multiple category not just one category, the code that i have its already using the checkbox but it only shows one category at a time hope u can help me with this
Hi Jogo,
I’m glad you liked it 🙃
First of all use different checkbox
name
attributes, for examplecategories_printi_1
,categories_printi_2
, but bettercategories_printi_{TERM ID}
.How can i set custom post type and category filter?
Check the above comment ;)
Welcome. Thank you for this code. Unfortunately, I have an error,
POST http://localhost:3000/Elisabete/wp-admin/admin-ajax.php 400 (Bad Request)
Elisabete is my domain name. I tried to combine, but I do not know what may be wrong.
I checked myself through the console.log
The console shows me:
http://localhost:3000/Elisabete/wp-admin/admin-ajax.php
misha_number_of_results=3&misha_order_by=date-DESC&action=mishafilter
So in theory everything is good. Do you have any idea what now?
Thank you.
Hey,
Double check please your action parameter – is it the same in PHP?
Hi Misha!
First of all thanks a lot! You are truly awesome.
And your code is amazing.
I had to try a lot before getting what I wanted but I feel like I’m close.
I have 3 custom post types (they have the same fields and act as macro categories but I needed them as separates types of posts), each one with their own taxonomy (CPT_1 with CTax_1, CPT_2 with Ctax_2..) and in their archive templates there is your ajax filter.
So, what I accomplished so far is this:
– set custom post type with hidden input
– set custom post taxonomy with another hidden input
– make filtering on input click (
)
– multiple filtering with unique checkboxes names inside a foreach:
And that’s all super cool!
But I have a problem with cleaning the form (show all).
The clear input for resetting the form was working until I added the multiple filtering.
I had only one input name for all the inputs (radios) and the clear filer had nothing as value (value””). But now even if I leave the input name for the filtering outside the above foreach it doesn’t clear the form.
I think this happens because of the checkboxes still being checked?
If I uncheck them one by one the form show all the results (as it should).
What do you think I could do to clear the checkboxes on a clear button click?
Also, as of now, the multiple filtering works in a subtractive way (two terms selected = only posts that have both will be shown). What if I wanted to filter in an additive way (two terms selected = all posts in both will be shown)?
Thanks a lot, Misha, you are the boss!
Kudos,
Hot
Hi Hot,
Thank you 🙃
The first question – to uncheck the checkboxes try this code
$("#checkbox").prop("checked", false);
.Second question – begin your
tax_query
with this:Misha!
Wow, thanks for the lightfast answer, spot on!
It was exactly what I needed.
I implemented it like this (so that when the checkbox with #show-all ID is selected all the checkboxes of the form, show-all included, are to be unchecked):
And thanks especially for this! I tried to put that same piece of code everywhere, like:
And never thought of actually putting it inside the array declaration!
Thanks a lot!
Always welcome! 🙃
But you did it a little bit incorrect
Hello Misha, is excellent tutorial, How could I adapt this with a custom post type? Only the filters work. The load more button does not work
Hello Joseph,
It depends on where are you going to use it? On CPT archive pages?
Hello dear Misha, I have a post type “videos” and I want to list in a page-videos.php I followed the procedure as is and the button does not work to load more, I think I should change these lines (HERE),
I do not know where else, please help me with this thing that is frustrating me
Hey.
Your script is great!
How to combine this topic and pagination?
That is ajax-load-more + with-filters + pagination
I think you need to pass the filter result to the misha_paginator function.
Have a nice day!
Hey Sergio,
There is a tut about that.
Thanks, you too!
Hi Misha,
It’s a great tutorial, but could you please create a new tutorial when you have time which will show custom post type (not post please) filter with either ajax pagination or load more? I am trying to do it for a custom post type in a custom page template. But not able to make it working properly. If you can create the new tutorial with CPT then it will be perfect. I believe in some cases the codes will be different which I am not able to figure out.
Thank you.
Hi Anjan,
I assume you’re trying to do it for a custom query, because for CPT archive pages everything should be OK. I answered about custom queries a lot in comments, please check. Here is one of them.
Hi Misha, you did a great job, but there’s a little typo here, almost at the beginning of the post:
Step 1. Creating “Posts per page” and “Orber by” filters. Load more button.
It’s “Order” and not “Orber”.
Apart from that, thank you, I really appreciate your tutorials. Cheers!
Hi Marco,
Thank you, fixed! 🙃