AJAX-powered “Load More” buttons are a great way to improve user experience by allowing users to load more posts dynamically without refreshing the page. This tutorial will walk you through the steps to implement a custom AJAX load more button on your WordPress site without relying on third-party plugins.
Why Use an AJAX Load More Button?
An AJAX Load More button improves site performance by loading posts dynamically as the user requests them, rather than loading all posts at once. This reduces initial page load time and allows users to control the content flow.
Looking for continuous scrolling instead of a button? See How to Add Infinite Scroll to WordPress with JavaScript.
Step 1: Modify Your WordPress Query
Before we can implement the AJAX functionality, we need to modify the WordPress query to support pagination. Add the following code to your theme’s functions.php file:
function my_load_more_posts() {
check_ajax_referer('load_more_nonce', 'nonce');
$paged = isset($_POST['page']) ? intval($_POST['page']) : 1;
$args = array(
'post_type' => 'post',
'posts_per_page' => 6,
'paged' => $paged,
);
$query = new WP_Query($args);
if ($query->have_posts()) :
while ($query->have_posts()) : $query->the_post();
get_template_part('template-parts/content', get_post_format());
endwhile;
endif;
wp_reset_postdata();
wp_die();
}
add_action('wp_ajax_my_load_more', 'my_load_more_posts');
add_action('wp_ajax_nopriv_my_load_more', 'my_load_more_posts');
This function handles the AJAX query for loading more posts. The check_ajax_referer() call verifies the nonce for security, and the two add_action hooks ensure it works for both logged-in and non-logged-in users.
Always verify a nonce in your AJAX handler. Without it, anyone can call your endpoint directly and trigger arbitrary queries.
Step 2: Enqueue the JavaScript
Now, we’ll enqueue the JavaScript that will trigger the AJAX call when the “Load More” button is clicked. Add this to your theme’s functions.php file:
function enqueue_load_more_script() {
wp_enqueue_script(
'load-more',
get_template_directory_uri() . '/js/load-more.js',
array('jquery'),
'1.0.0',
true
);
wp_localize_script('load-more', 'load_more_params', array(
'ajax_url' => admin_url('admin-ajax.php'),
'nonce' => wp_create_nonce('load_more_nonce'),
'current_page' => 1,
'max_page' => $GLOBALS['wp_query']->max_num_pages,
));
}
add_action('wp_enqueue_scripts', 'enqueue_load_more_script');
This function enqueues the JavaScript file and passes the AJAX URL, a security nonce, and the maximum number of pages to the script via wp_localize_script().
Step 3: Create the JavaScript File
Next, create a new file named load-more.js in your theme’s js folder. Add the following JavaScript code:
jQuery(function($) {
var canLoadMore = true;
var currentPage = load_more_params.current_page;
var maxPage = load_more_params.max_page;
$('#load-more-button').on('click', function() {
if (currentPage < maxPage && canLoadMore) {
canLoadMore = false;
currentPage++;
$.ajax({
url: load_more_params.ajax_url,
type: 'POST',
data: {
action: 'my_load_more',
page: currentPage,
nonce: load_more_params.nonce
},
beforeSend: function() {
$('#load-more-button').text('Loading...').prop('disabled', true);
},
success: function(response) {
if (response) {
$('.post-list').append(response);
canLoadMore = true;
$('#load-more-button').text('Load More').prop('disabled', false);
if (currentPage >= maxPage) {
$('#load-more-button').hide();
}
}
},
error: function() {
$('#load-more-button').text('Load More').prop('disabled', false);
canLoadMore = true;
}
});
}
});
});
This script triggers the AJAX request when the user clicks the “Load More” button. It increments the current page and appends new posts to the existing list.
Step 4: Add the Load More Button to Your Template
Now, you need to add the “Load More” button to your theme’s post listing template. Find the relevant file, such as index.php or archive.php, and add the button after the post loop:
<?php
echo '<div class="post-list">';
?>
<?php if ( have_posts() ) : while ( have_posts() ) : the_post(); ?>
<?php get_template_part('template-parts/content', get_post_format()); ?>
<?php endwhile; endif; ?>
<?php
echo '</div>';
?>
<button id="load-more-button">Load More</button>This button will trigger the JavaScript to load more posts when clicked.
Browser Compatibility and Performance Optimization
Although this solution should work in most modern browsers, it’s a good practice to test across different browsers and devices. If performance is an issue due to multiple AJAX calls, you can optimize the scroll event using requestAnimationFrame. Here’s an example:
var ticking = false;
$('#load-more-button').on('click', function() {
if (!ticking) {
window.requestAnimationFrame(function() {
// Load more logic here
ticking = false;
});
ticking = true;
}
});
This ensures that multiple AJAX calls aren’t triggered at the same time, improving performance.
Adding a Fallback for Pagination
If the “Load More” button fails for any reason, or if a user has JavaScript disabled, it’s a good idea to provide a fallback with traditional pagination links. Add the following code to your post loop template:
if (function_exists('the_posts_pagination')) {
the_posts_pagination();
}
This ensures that users can still access additional posts through regular pagination.
SEO Considerations for AJAX Load More
An AJAX “Load More” button improves user experience, but search engines may not execute JavaScript to discover content beyond the initial page. To keep your content crawlable:
- Provide a pagination fallback – ensure all content is accessible via traditional pagination links (see the fallback section above). This gives crawlers a static path to every post.
- Use structured data – add Schema.org markup to help search engines understand your content structure.
- Verify in Google Search Console – use the URL Inspection tool to confirm Google can see your paginated content.
Google dropped support for
rel="next"andrel="prev"in 2019. You do not need to add these attributes for SEO purposes.
Handling Edge Cases and Errors
It’s important to handle cases where there are no more posts to load or when an AJAX request fails. Modify the JavaScript to check if more posts are available and display a message when all posts are loaded:
if (!response || currentPage >= maxPage) {
canLoadMore = false;
$('#load-more-button').text('No more posts to load');
} else {
$('.post-list').append(response);
canLoadMore = true;
}
This ensures a smooth experience for users, even if they reach the end of the content.
FAQs
Common questions about implementing an AJAX Load More button in WordPress:
paged parameter is not passed to WP_Query. Make sure currentPage is incremented before sending the request and that the server-side handler reads it with intval($_POST['page']).wp_localize_script() and compare it with the current page after each request. When currentPage >= maxPage, hide or remove the button. The code examples in this post already include this check.$.ajax() call with the native fetch() API. Use fetch(ajaxUrl, { method: 'POST', body: formData }) instead, and parse the response with .text(). Remove array('jquery') from wp_enqueue_script() dependencies if you go this route.admin-ajax.php directly with arbitrary parameters. WordPress nonces are time-limited tokens that verify the request came from your site.post_type parameter in the WP_Query arguments from 'post' to your custom post type slug (e.g., 'product' or 'portfolio'). You can also pass the post type from the frontend via the AJAX data object for a more flexible setup.Summary
An AJAX “Load More” button lets users load additional posts without a page reload, reducing initial load time and improving engagement.
The implementation requires four parts: a PHP AJAX handler with nonce verification, an enqueue function that passes parameters to JavaScript, a JS file that sends the request and appends the response, and a template button.
Always include a traditional pagination fallback for users with JavaScript disabled and for search engine crawlers.

