AJAX Filter Posts by Category with Pagination

In this tutorial we’re going to learn how to create an AJAX pagination (without page refresh) on your WordPress website and also how to make it work together with AJAX filters (if you have one of course).

As a result of this tutorial, we’re going to have something like this:

WordPress AJAX filter posts by category with pagination

On the screenshot, you can see that we’re going to use page numbers links as a pagination, but using the same approach, you can easily make it with a load more button.

Creating a WordPress AJAX Pagination without a Plugin

First things first, we will begin with creating an AJAX pagination without any plugins. Depending on whether you’re using a block theme or a classic theme, the whole process can be absolutely different.

WordPress AJAX pagination without plugins example
This specific AJAX pagination is created with the standard Query Loop block.

Using the Query Loop block in Block Themes

In order to create an AJAX pagination without plugins for a block theme all you need to do is to open the Query Loop block settings and make sure that the “Force page reload” toggle is unchecked.

Force page reload settings in WordPress Query Loop block
In order to create an AJAX pagination in a Block Theme, just uncheck this checkbox.

That’s basically all you need to do 😁 How cool is that? The only thing is that if your Query Loop block contains any blocks that don’t support Interactivity API, then probably it won’t be possible until you remove those blocks.

Creating an AJAX Pagination for Classic Themes

Since we decided that we’re going to create a WordPress AJAX pagination without any plugins, this part can be a little bit complicated, especially compared to the previous part, at least because here we need to manually send AJAX requests.

First thing first, let’s assume that we have the following HTML elements in our classic theme:

  • nav.pagination – it is a <div> element with page numbers navigation, each link has a .page-numbers CSS class and a current one has .current class.
  • #site-content – it is where the posts are displayed.

So, every time we send an AJAX request when using a pagination we need to populate both HTML elements with the updated content.

Ok, let’s try to send an AJAX request right now. I’m going to use jQuery for this purpose here in order to simplify the whole thing.

jQuery( function( $ ) {
	
	$( 'body' ).on( 'click', '.page-numbers', function( event ) {
		// prevent links from being clicked otherwise a page will be reloaded
		event.preventDefault()
	
		// also do nothing if we are already on this page
		if( $(this).hasClass( 'current' ) ) {
			return
		}
		
		// in most cases we can get a page number we want to navigate to from a link text
		const pageNumber = $(this).text()
	
		$.ajax( {
			url : pagination_and_filter_args.ajaxurl,
			data : {
				'action' : 'misha_paginate',
				'query': pagination_and_filter_args.query,
				'page' : pageNumber,
			},
			 // this data type allows us to receive objects from the server
			dataType : 'json',
			type : 'POST',
			beforeSend : function( xhr ){
				// we can add some preloading animation here
			},
			success : function( data ){
				// display posts
				$( '#site-content' ).html( data.posts )
				// display an updated new pagination
				$( 'nav.pagination' ).html( data.pagination )
			}
		} )
		
	} )
	
} )

Probably at this point, you may have the following questions:

  • Where to use this code?
  • What is pagination_and_filter_args?
  • How to process this AJAX request with PHP?

So let’s start step by step, and let me answer the first two questions first.

I recommend you create a separate JavaScript file for this code and enqueue it the following way in the functions.php of your theme:

add_action( 'wp_enqueue_scripts', function() {

	global $wp_query; 
	
	wp_register_script(
		'my_pagination_and_filter',
		get_stylesheet_directory_uri() . '/my_pagination_and_filter.js',
		array( 'jquery' ),
		time(),
		true
	);
 
	wp_localize_script( 
		'my_pagination_and_filter', 
		'pagination_and_filter_args', 
		array(
			'ajaxurl' => admin_url( 'admin-ajax.php' ),
			'query' => json_encode( $wp_query->query_vars ),
		)
	);

 	wp_enqueue_script( 'my_pagination_and_filter' );

} );

As you can see, with the help of the wp_localize_script() function I also included the pagination_and_filter_args parameter, so it will be available in our JavaScript file.

The only pitfall here is that providing the value of a query variable like this will only allow you to use this AJAX pagination for the main loop. So, if you’re going to create any additional loops on your page with WP_Query, then you need to look for some alternative ways to provide the query variables into the AJAX request. You could probably try to use a paginate_links_output filter for that purpose. Or just use a block theme 😁

Last but not least let’s process this AJAX request in PHP:

// add_action( 'wp_ajax_{ACTION VALUE}', ...
add_action( 'wp_ajax_misha_paginate', 'rudr_ajax_pagination' ); 
add_action( 'wp_ajax_nopriv_misha_paginate', 'rudr_ajax_pagination' );
 
function rudr_ajax_pagination(){
 
	// prepare our arguments for the query
	// query_posts() will take care of the necessary sanitization
	$args = json_decode( stripslashes( $_POST[ 'query' ] ), true );

	// update a page number
	$args[ 'paged' ] = $_POST[ 'page' ];
	$args[ 'post_status' ] = 'publish';

	$posts_html = '';

	query_posts( $args );
 
	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 Twenty theme
			get_template_part( 'template-parts/content', get_post_type() );
 
		endwhile;
 
		$posts_html = ob_get_contents(); // we pass the posts into a variable
		ob_end_clean(); // clear the buffer
	endif;
	
	$pagination_html = get_the_posts_pagination( 
		array( 
			'mid_size' => 1, 
			'prev_next' => false,
		)
	);
 
	// no wp_reset_query() required
 
 	echo json_encode( array(
		'posts' => $posts_html,
		'pagination' => $pagination_html,
	) );
 
	die();
}

In this code snippet, I would like you to pay attention to the following moments:

  • If you don’t provide a post status $args[ 'post_status' ] = 'publish', then drafts are also going to be included, which will definitely break our pagination.
  • The best way to get a post template in a classic WordPress theme is to use the get_template_part() function, but this function can not return its value, it always prints it, that’s why we have to use buffer functions ob_start(), ob_get_contents() and ob_end_clean() so we can run get_template_part() multiple times and then add everything that was printed to a variable.
  • Instead of json_encode() PHP function you can feel free to use wp_send_json_success() if it is more convenient for you.
  • get_the_posts_pagination() is also a very convenient function when we need to print a pagination within a loop, using paginate_links() doesn’t make sense here.

Combining AJAX Category Filter with Pagination

Ok but if you want to filter posts by category with pagination, all in AJAX? Is there a simple way to combine an AJAX filter with an AJAX pagination?

WordPress AJAX filter posts by category with pagination
This specific AJAX category filter with pagination is created with the standard Query Loop block and my Taxonomy Filter Block plugin.

Once again, there are two completely different approaches depending on whether you’re using a classic or a block theme.

AJAX filter posts by category with pagination in Block Themes

If you’re going to code everything from scratch here, then I have bad news – you need to learn Gutenberg block development deeply together with Interactivity API.

So here I would recommend you to just use my Taxonomy Filter Block plugin instead which works great with the default AJAX pagination in WordPress block themes.

All you need to do is to install and activate the plugin and add the taxonomy filter block into a desirable query loop block. What is really great here is that it works for additional loops on the page not only for the main loop.

AJAX filter with pagination in Classic Themes

Probably here it would be better to read a tutorial about creating an AJAX category filter first. I think it is not necessary to copy all the code I had already described there, so I am going to skip the HTML part. Everything else we’re going to uncover below.

Ok, first of all, let’s create a JavaScript event with an AJAX request for a category filter:

jQuery( function( $ ) {
	
	$( '#ajax-filter select' ).change( function() {
		
		const categoryId = $(this).val()
			
		$.ajax( {
			url : pagination_and_filter_args.ajaxurl,
			data : {
				'action' : 'misha_filter',
				'query': pagination_and_filter_args.query,
				'category_id' : categoryId
			},
			 // this data type allows us to receive objects from the server
			dataType : 'json',
			type : 'POST',
			beforeSend : function( xhr ){
				// we can do some preloading animation here
			},
			success : function( data ){
				// display posts
				$( '#site-content' ).html( data.posts )
				// display an updated new pagination
				$( 'nav.pagination' ).html( data.pagination )
				// change the query vars
				pagination_and_filter_args.query = data.query
				
			}
		} )
		
	} )
	
} );

And now, let’s process this AJAX filtering request in PHP:

// add_action( 'wp_ajax_{ACTION VALUE}', ...
add_action( 'wp_ajax_misha_filter', 'rudr_ajax_filter' ); 
add_action( 'wp_ajax_nopriv_misha_filter', 'rudr_ajax_filter' );
 
function rudr_ajax_filter(){
 
	// prepare our arguments for the query
	// query_posts() takes care of the necessary sanitization
	$args = json_decode( stripslashes( $_POST[ 'query' ] ), true );

	$args[ 'cat' ] = $_POST[ 'category_id' ];
	$args[ 'post_status' ] = 'publish';
	$args[ 'paged' ] = 1;
	
	$posts_html = '';

	query_posts( $args );
 
	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 Twenty theme
			get_template_part( 'template-parts/content', get_post_type() );
 
		endwhile;
 
		$posts_html = ob_get_contents(); // we pass the posts to variable
		ob_end_clean(); // clear the buffer
	endif;
	
	$pagination_html = get_the_posts_pagination( 
		array( 
			'mid_size' => 1, 
			'prev_next' => false,
		)
	);
 
	// no wp_reset_query() required
 
 	echo json_encode( array(
		'posts' => $posts_html,
		'pagination' => $pagination_html,
		'query' => json_encode( $wp_query->query_vars ),
	) );
 
	die();
}

The code is very similar to the one we used before when we were creating an AJAX pagination without a filter. The only thing to keep in mind is that we have to update the query parameters pagination_and_filter_args.query after filtering posts otherwise, the pagination won’t work.

Misha Rudrastyh

Misha Rudrastyh

Hey guys and welcome to my website. For more than 15 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

Follow me on X